pax_global_header00006660000000000000000000000064152323766550014530gustar00rootroot0000000000000052 comment=3fae4b0166decd6c13372603f7455e6cb39ed9bb pg_exporter-1.4.1/000077500000000000000000000000001523237665500140715ustar00rootroot00000000000000pg_exporter-1.4.1/.dockerignore000066400000000000000000000001201523237665500165360ustar00rootroot00000000000000* !Dockerfile !go.mod !go.sum !main.go !exporter/ !exporter/** !pg_exporter.yml pg_exporter-1.4.1/.github/000077500000000000000000000000001523237665500154315ustar00rootroot00000000000000pg_exporter-1.4.1/.github/workflows/000077500000000000000000000000001523237665500174665ustar00rootroot00000000000000pg_exporter-1.4.1/.github/workflows/ci.yaml000066400000000000000000000030301523237665500207410ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true - name: Verify module files run: go mod tidy -diff - name: Verify generated configs run: make conf conf9 && git diff --exit-code pg_exporter.yml legacy/pg_exporter.yml - name: Verify standalone RPM package metadata run: | for config in package/nfpm-amd64-rpm.yaml package/nfpm-arm64-rpm.yaml; do grep -qx 'name: "pg-exporter"' "$config" grep -qx 'provides:' "$config" grep -qx 'replaces:' "$config" test "$(grep -c '^ - pg_exporter$' "$config")" -eq 2 done - name: Run tests run: go test -race -count=1 ./... - name: Run go vet run: go vet ./... - name: Cross-build supported targets run: | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./... CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build ./... CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le go build ./... CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build ./... CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build ./... CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build ./... pg_exporter-1.4.1/.github/workflows/release.yaml000066400000000000000000000023011523237665500217660ustar00rootroot00000000000000name: Release on: push: tags: - 'v*.*.*' permissions: contents: write jobs: release: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: 'go.mod' cache: true - name: Run unit tests run: go test ./... - name: Run go vet run: go vet ./... - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser version: latest args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload artifacts uses: actions/upload-artifact@v4 if: always() with: name: dist path: dist/ pg_exporter-1.4.1/.github/workflows/test-release.yaml000066400000000000000000000040301523237665500227440ustar00rootroot00000000000000name: Test Release on: workflow_dispatch: # �许手动触� pull_request: paths: - '.goreleaser.yml' - 'package/nfpm-*-rpm.yaml' - '.github/workflows/release.yaml' - '.github/workflows/test-release.yaml' permissions: contents: read jobs: test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: 'go.mod' cache: true - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Run unit tests run: go test ./... - name: Run go vet run: go vet ./... - name: Test GoReleaser config uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser version: latest args: check - name: Build snapshot uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser version: latest args: release --snapshot --clean --skip=publish,docker - name: Verify artifacts run: | echo "Generated artifacts:" ls -lh dist/*.tar.gz || true ls -lh dist/*.rpm shopt -s nullglob rpms=(dist/*.rpm) test "${#rpms[@]}" -gt 0 for rpm in "${rpms[@]}"; do [[ "$(basename "$rpm")" == pg-exporter-*.rpm ]] || { echo "::error file=.goreleaser.yml::unexpected RPM artifact name: $(basename "$rpm")" exit 1 } done docker run --rm -v "$PWD/dist:/dist:ro" rockylinux:9 bash -euo pipefail -c ' for rpm in /dist/*.rpm; do rpm -qp --provides "$rpm" | grep -qx pg_exporter rpm -qp --obsoletes "$rpm" | grep -qx pg_exporter done ' echo "" echo "Checksums:" cat dist/checksums.txt || true pg_exporter-1.4.1/.gitignore000066400000000000000000000003321523237665500160570ustar00rootroot00000000000000# binary files pg_exporter # tmp files test/ deploy/ upload.sh temp/ dist/ .DS_Store # IDE files .vscode/ .idea/ .code/ .claude .codex/ .codex_tmp/ pg_exporter.iml CLAUDE.md .hugo_build.lock public/ resources/ tmp/ pg_exporter-1.4.1/.goreleaser.yml000066400000000000000000000153511523237665500170270ustar00rootroot00000000000000version: 2 env: - CGO_ENABLED=0 before: hooks: - go mod download - go mod tidy builds: - id: pg_exporter main: ./main.go binary: pg_exporter goos: - linux - darwin - windows goarch: - amd64 - arm64 - ppc64le goarm: - 6 - 7 goamd64: - v1 ignore: # Darwin only supports amd64 and arm64 - goos: darwin goarch: ppc64le # Windows only supports amd64 and 386 - goos: windows goarch: arm64 - goos: windows goarch: arm - goos: windows goarch: ppc64le ldflags: - -s -w - -extldflags "-static" # Release versions are unprefixed, while the snapshot version below is # derived from a v-prefixed tag; normalize both to exactly one leading v. - -X 'pg_exporter/exporter.Version=v{{ trimprefix .Version "v" }}' - -X 'pg_exporter/exporter.Branch={{.Branch}}' - -X 'pg_exporter/exporter.Revision={{.ShortCommit}}' - -X 'pg_exporter/exporter.BuildDate={{.Date}}' flags: - -a archives: - id: pg_exporter name_template: >- {{ .ProjectName }}-{{ .Version }}.{{ .Os }}- {{- if eq .Arch "amd64" }}amd64 {{- else if eq .Arch "386" }}386 {{- else if eq .Arch "arm64" }}arm64 {{- else if eq .Arch "arm" }}armv{{ .Arm }} {{- else if eq .Arch "ppc64le" }}ppc64le {{- else }}{{ .Arch }}{{ end }} files: - pg_exporter.yml - LICENSE - package/pg_exporter.default - package/pg_exporter.service nfpms: - id: pg_exporter_rpm package_name: pg-exporter file_name_template: >- {{ .PackageName }}-{{ .Version }}-{{ .Release }}. {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "arm64" }}aarch64 {{- else }}{{ .Arch }}{{ end }} vendor: PGSTY homepage: https://pigsty.io/docs/pg_exporter maintainer: Ruohang Feng description: | Prometheus exporter for PostgreSQL / Pgbouncer server metrics. Supported version: Postgres9.x - 19+ & Pgbouncer 1.8 - 1.25+ Part of Project Pigsty -- Battery Included PostgreSQL Distribution with ultimate observability support: https://pigsty.io/docs/pg_exporter license: Apache-2.0 formats: - rpm bindir: /usr/bin release: "1" section: database priority: optional provides: - pg_exporter replaces: - pg_exporter contents: - src: pg_exporter.yml dst: /etc/pg_exporter.yml type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.default dst: /etc/default/pg_exporter type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.service dst: /usr/lib/systemd/system/pg_exporter.service type: config - src: LICENSE dst: /usr/share/doc/pg_exporter/LICENSE file_info: mode: 0644 scripts: preinstall: package/preinstall.sh rpm: compression: gzip prefixes: - /usr/bin - id: pg_exporter_deb package_name: pg-exporter file_name_template: >- {{ .PackageName }}_{{ .Version }}-{{ .Release }}_ {{- if eq .Arch "amd64" }}amd64 {{- else if eq .Arch "arm64" }}arm64 {{- else }}{{ .Arch }}{{ end }} vendor: PGSTY homepage: https://pigsty.io/docs/pg_exporter maintainer: Ruohang Feng description: | Prometheus exporter for PostgreSQL / Pgbouncer server metrics. Supported version: Postgres9.x - 19+ & Pgbouncer 1.8 - 1.25+ Part of Project Pigsty -- Battery Included PostgreSQL Distribution with ultimate observability support: https://pigsty.io/docs/pg_exporter license: Apache-2.0 formats: - deb bindir: /usr/bin release: "1" section: database priority: optional contents: - src: pg_exporter.yml dst: /etc/pg_exporter.yml type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.default dst: /etc/default/pg_exporter type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.service dst: /lib/systemd/system/pg_exporter.service type: config - src: LICENSE dst: /usr/share/doc/pg_exporter/LICENSE file_info: mode: 0644 scripts: preinstall: package/preinstall.sh checksum: name_template: 'checksums.txt' algorithm: sha256 snapshot: version_template: "{{ .Tag }}-next" changelog: sort: asc filters: exclude: - '^docs:' - '^test:' - '^chore:' - 'Merge pull request' - 'Merge branch' release: github: owner: pgsty name: pg_exporter draft: false prerelease: false mode: replace # Replace existing release with same tag replace_existing_artifacts: true # Replace existing artifacts name_template: "{{.ProjectName}}-v{{.Version}}" disable: false discussion_category_name: "" # Skip discussion creation announce: skip: true # Skip all announcements # Docker configuration for multi-arch images dockers: - id: pg_exporter_amd64 ids: - pg_exporter goos: linux goarch: amd64 image_templates: - "pgsty/pg_exporter:{{ .Version }}-amd64" - "pgsty/pg_exporter:latest-amd64" dockerfile: Dockerfile.goreleaser use: buildx build_flag_templates: - "--platform=linux/amd64" - "--label=org.opencontainers.image.version={{.Version}}" - "--label=org.opencontainers.image.created={{.Date}}" - "--label=org.opencontainers.image.revision={{.FullCommit}}" extra_files: - pg_exporter.yml - LICENSE - id: pg_exporter_arm64 ids: - pg_exporter goos: linux goarch: arm64 image_templates: - "pgsty/pg_exporter:{{ .Version }}-arm64" - "pgsty/pg_exporter:latest-arm64" dockerfile: Dockerfile.goreleaser use: buildx build_flag_templates: - "--platform=linux/arm64" - "--label=org.opencontainers.image.version={{.Version}}" - "--label=org.opencontainers.image.created={{.Date}}" - "--label=org.opencontainers.image.revision={{.FullCommit}}" extra_files: - pg_exporter.yml - LICENSE docker_manifests: - name_template: "pgsty/pg_exporter:{{ .Version }}" image_templates: - "pgsty/pg_exporter:{{ .Version }}-amd64" - "pgsty/pg_exporter:{{ .Version }}-arm64" - name_template: "pgsty/pg_exporter:latest" image_templates: - "pgsty/pg_exporter:latest-amd64" - "pgsty/pg_exporter:latest-arm64" pg_exporter-1.4.1/Dockerfile000066400000000000000000000025251523237665500160670ustar00rootroot00000000000000# syntax=docker/dockerfile:1 FROM golang:1.26.5-alpine AS builder-env ARG GOPROXY=https://proxy.golang.org,direct ARG GOSUMDB=sum.golang.org ENV GOPROXY=${GOPROXY} ENV GOSUMDB=${GOSUMDB} # Build a self-contained pg_exporter container with a clean environment and no # dependencies. # # build with # # docker buildx build -f Dockerfile --tag pg_exporter . # WORKDIR /build COPY go.mod go.sum ./ RUN \ --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=linux go mod download COPY . /build RUN \ --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=linux go build -a -o /pg_exporter . FROM scratch LABEL org.opencontainers.image.authors="Ruohang Feng , Craig Ringer " \ org.opencontainers.image.url="https://github.com/pgsty/pg_exporter" \ org.opencontainers.image.source="https://github.com/pgsty/pg_exporter" \ org.opencontainers.image.licenses="Apache-2.0" \ org.opencontainers.image.title="pg_exporter" \ org.opencontainers.image.description="PostgreSQL/Pgbouncer metrics exporter for Prometheus" WORKDIR /bin COPY --from=builder-env /pg_exporter /bin/pg_exporter COPY pg_exporter.yml /etc/pg_exporter.yml EXPOSE 9630/tcp ENTRYPOINT ["/bin/pg_exporter"] pg_exporter-1.4.1/Dockerfile.goreleaser000066400000000000000000000013111523237665500202060ustar00rootroot00000000000000# Dockerfile for goreleaser # This uses pre-built binaries from goreleaser instead of building from source FROM scratch LABEL org.opencontainers.image.authors="Ruohang Feng " \ org.opencontainers.image.url="https://github.com/pgsty/pg_exporter" \ org.opencontainers.image.source="https://github.com/pgsty/pg_exporter" \ org.opencontainers.image.licenses="Apache-2.0" \ org.opencontainers.image.title="pg_exporter" \ org.opencontainers.image.description="PostgreSQL/Pgbouncer metrics exporter for Prometheus" WORKDIR /bin COPY pg_exporter /bin/pg_exporter COPY pg_exporter.yml /etc/pg_exporter.yml COPY LICENSE /LICENSE EXPOSE 9630/tcp ENTRYPOINT ["/bin/pg_exporter"]pg_exporter-1.4.1/LICENSE000066400000000000000000000261461523237665500151070ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [2019-2025] [Ruohang Feng](rh@vonng.com) 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. pg_exporter-1.4.1/Makefile000066400000000000000000000167371523237665500155470ustar00rootroot00000000000000#==============================================================# # File : Makefile # Mtime : 2025-08-14 # License : Apache-2.0 @ https://github.com/pgsty/pg_exporter # Copyright : 2018-2026 Ruohang Feng / Vonng (rh@vonng.com) #==============================================================# VERSION ?= v1.4.1 DOCKER_REPO ?= pgsty/pg_exporter BUILD_DATE := $(shell date '+%Y%m%d%H%M%S') GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") GIT_REVISION := $(shell git rev-parse --short HEAD 2>/dev/null || echo "HEAD") LDFLAGS_META := -X 'pg_exporter/exporter.Version=$(VERSION)' \ -X 'pg_exporter/exporter.Branch=$(GIT_BRANCH)' \ -X 'pg_exporter/exporter.Revision=$(GIT_REVISION)' \ -X 'pg_exporter/exporter.BuildDate=$(BUILD_DATE)' LDFLAGS_STATIC := -s -w -extldflags \"-static\" $(LDFLAGS_META) # Release Dir LINUX_AMD_DIR:=dist/$(VERSION)/pg_exporter-$(VERSION).linux-amd64 LINUX_ARM_DIR:=dist/$(VERSION)/pg_exporter-$(VERSION).linux-arm64 DARWIN_AMD_DIR:=dist/$(VERSION)/pg_exporter-$(VERSION).darwin-amd64 DARWIN_ARM_DIR:=dist/$(VERSION)/pg_exporter-$(VERSION).darwin-arm64 ############################################################### # Shortcuts # ############################################################### build: go build -ldflags "$(LDFLAGS_META)" -o pg_exporter clean: rm -rf pg_exporter build-darwin-amd64: CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -ldflags "$(LDFLAGS_STATIC)" -o pg_exporter build-darwin-arm64: CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -a -ldflags "$(LDFLAGS_STATIC)" -o pg_exporter build-linux-amd64: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags "$(LDFLAGS_STATIC)" -o pg_exporter build-linux-arm64: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -a -ldflags "$(LDFLAGS_STATIC)" -o pg_exporter r: release release: release-linux release-darwin release-linux: linux-amd64 linux-arm64 linux-amd64: clean build-linux-amd64 rm -rf $(LINUX_AMD_DIR) && mkdir -p $(LINUX_AMD_DIR) nfpm package --packager rpm --config package/nfpm-amd64-rpm.yaml --target dist/$(VERSION) nfpm package --packager deb --config package/nfpm-amd64-deb.yaml --target dist/$(VERSION) cp pg_exporter $(LINUX_AMD_DIR)/pg_exporter cp pg_exporter.yml $(LINUX_AMD_DIR)/pg_exporter.yml cp LICENSE $(LINUX_AMD_DIR)/LICENSE tar -czf dist/$(VERSION)/pg_exporter-$(VERSION).linux-amd64.tar.gz -C dist/$(VERSION) pg_exporter-$(VERSION).linux-amd64 rm -rf $(LINUX_AMD_DIR) linux-arm64: clean build-linux-arm64 rm -rf $(LINUX_ARM_DIR) && mkdir -p $(LINUX_ARM_DIR) nfpm package --packager rpm --config package/nfpm-arm64-rpm.yaml --target dist/$(VERSION) nfpm package --packager deb --config package/nfpm-arm64-deb.yaml --target dist/$(VERSION) cp pg_exporter $(LINUX_ARM_DIR)/pg_exporter cp pg_exporter.yml $(LINUX_ARM_DIR)/pg_exporter.yml cp LICENSE $(LINUX_ARM_DIR)/LICENSE tar -czf dist/$(VERSION)/pg_exporter-$(VERSION).linux-arm64.tar.gz -C dist/$(VERSION) pg_exporter-$(VERSION).linux-arm64 rm -rf $(LINUX_ARM_DIR) release-darwin: darwin-amd64 darwin-arm64 darwin-amd64: clean build-darwin-amd64 rm -rf $(DARWIN_AMD_DIR) && mkdir -p $(DARWIN_AMD_DIR) cp pg_exporter $(DARWIN_AMD_DIR)/pg_exporter cp pg_exporter.yml $(DARWIN_AMD_DIR)/pg_exporter.yml cp LICENSE $(DARWIN_AMD_DIR)/LICENSE tar -czf dist/$(VERSION)/pg_exporter-$(VERSION).darwin-amd64.tar.gz -C dist/$(VERSION) pg_exporter-$(VERSION).darwin-amd64 rm -rf $(DARWIN_AMD_DIR) darwin-arm64: clean build-darwin-arm64 rm -rf $(DARWIN_ARM_DIR) && mkdir -p $(DARWIN_ARM_DIR) cp pg_exporter $(DARWIN_ARM_DIR)/pg_exporter cp pg_exporter.yml $(DARWIN_ARM_DIR)/pg_exporter.yml cp LICENSE $(DARWIN_ARM_DIR)/LICENSE tar -czf dist/$(VERSION)/pg_exporter-$(VERSION).darwin-arm64.tar.gz -C dist/$(VERSION) pg_exporter-$(VERSION).darwin-arm64 rm -rf $(DARWIN_ARM_DIR) ############################################################### # Configuration # ############################################################### # generate merged config from separated configuration conf: rm -rf pg_exporter.yml cat config/*.yml >> pg_exporter.yml # generate legacy merged config for PostgreSQL 9.1 - 9.6 conf9: rm -rf legacy/pg_exporter.yml cat legacy/config/*.yml >> legacy/pg_exporter.yml # Backward-compatible alias (deprecated) conf-pg9: conf9 ############################################################### # Release # ############################################################### release-dir: mkdir -p dist/$(VERSION) release-clean: rm -rf dist/$(VERSION) ############################################################### # GoReleaser # ############################################################### # Install goreleaser if not present goreleaser-install: @which goreleaser > /dev/null || (echo "Installing goreleaser..." && go install github.com/goreleaser/goreleaser/v2@latest) # Build snapshot release (without publishing) goreleaser-snapshot: goreleaser-install goreleaser release --snapshot --clean --skip=publish # Build release locally (without git tag) goreleaser-build: goreleaser-install goreleaser build --snapshot --clean # Build release locally without snapshot suffix (requires clean git) goreleaser-local: goreleaser-install goreleaser release --clean --skip=publish # Release with goreleaser (requires git tag) goreleaser-release: goreleaser-install goreleaser release --clean # Test release (creates prerelease, no notifications) goreleaser-test-release: goreleaser-install @echo "Creating test release (prerelease mode, no notifications)..." goreleaser release --clean # Production release (set prerelease to false in config first) goreleaser-prod-release: goreleaser-install @echo "Creating production release (will notify subscribers if announce.skip is false)..." goreleaser release --clean # Check goreleaser configuration goreleaser-check: goreleaser-install goreleaser check # New main release task using goreleaser release-new: goreleaser-release # Build a local image from the canonical Dockerfile. # Multi-architecture release images are built by GoReleaser. docker: docker-build docker-build: docker build -f Dockerfile \ --build-arg GOPROXY=$${GOPROXY:-https://proxy.golang.org,direct} \ --build-arg GOSUMDB=$${GOSUMDB:-sum.golang.org} \ -t $(DOCKER_REPO):$(VERSION)-dev \ -t $(DOCKER_REPO):dev . ############################################################### # Develop # ############################################################### install: build sudo install -m 0755 pg_exporter /usr/bin/pg_exporter uninstall: sudo rm -rf /usr/bin/pg_exporter runb: ./pg_exporter --log.level=info --config=pg_exporter.yml --auto-discovery run: go run main.go --log.level=info --config=pg_exporter.yml --auto-discovery debug: go run main.go --log.level=debug --config=pg_exporter.yml --auto-discovery curl: curl localhost:9630/metrics | grep -v '#' | grep pg_ upload: ./upload.sh d: dev dev: hugo serve .PHONY: build clean build-darwin-amd64 build-darwin-arm64 build-linux-amd64 build-linux-arm64 \ r release release-darwin release-linux linux-amd64 linux-arm64 darwin-amd64 darwin-arm64 \ docker docker-build \ install uninstall debug curl upload \ goreleaser-install goreleaser-snapshot goreleaser-build goreleaser-release goreleaser-test-release \ goreleaser-check release-new goreleaser-local pg_exporter-1.4.1/README.md000066400000000000000000001237011523237665500153540ustar00rootroot00000000000000

PG Exporter Logo

# PG EXPORTER [![Webite: https://pigsty.io/docs/pg_exporter](https://img.shields.io/badge/website-pigsty.io/docs/pg_exporter-slategray?style=flat&logo=cilium&logoColor=white)](https://pigsty.io/docs/pg_exporter) [![DockerHub: pgsty/pg_exporter](https://img.shields.io/badge/docker-pgsty/pg_exporter-slategray?style=flat&logo=docker&logoColor=white)](https://hub.docker.com/r/pgsty/pg_exporter) [![Version: 1.4.1](https://img.shields.io/badge/version-1.4.1-slategray?style=flat&logo=cilium&logoColor=white)](https://github.com/pgsty/pg_exporter/releases/tag/v1.4.1) [![License: Apache-2.0](https://img.shields.io/github/license/pgsty/pg_exporter?logo=opensourceinitiative&logoColor=green&color=slategray)](https://github.com/pgsty/pg_exporter/blob/main/LICENSE) [![GitHub Stars](https://img.shields.io/github/stars/pgsty/pg_exporter?style=flat&logo=github&logoColor=black&color=slategray)](https://star-history.com/#pgsty/pg_exporter&Date) [![Go Report Card](https://goreportcard.com/badge/github.com/pgsty/pg_exporter)](https://goreportcard.com/report/github.com/pgsty/pg_exporter) > **Advanced [PostgreSQL](https://www.postgresql.org) & [pgBouncer](https://www.pgbouncer.org/) metrics [exporter](https://prometheus.io/docs/instrumenting/exporters/) for [Prometheus](https://prometheus.io/)** PG Exporter brings ultimate monitoring experience to your PostgreSQL with **declarative config**, **dynamic planning**, and **customizable collectors**. It provides **600+** metrics and ~3K time series per instance, covers everything you'll need for PostgreSQL observability. Check [**https://demo.pigsty.io**](https://demo.pigsty.io/ui/) for live demo, which is built upon this exporter by [**Pigsty**](https://pigsty.io).
Docs • Quick Start • Features • Usage • API • Deployment • Collectors • Demo

[![pigsty-dashboard](https://pigsty.io/img/pigsty/dashboard.jpg)](https://demo.pigsty.io) -------- ## Features - **Highly Customizable**: Define almost all metrics through declarative YAML configs - **Full Coverage**: Monitor PostgreSQL (10-19+) and pgBouncer (1.8-1.25+) in a single exporter - **Fine-grained Control**: Configure timeout, caching, skip conditions, and fatality per collector - **Dynamic Planning**: Define multiple query branches based on different conditions - **Self-monitoring**: Rich metrics about pg_exporter [itself](https://demo.pigsty.io/d/pgsql-exporter) for complete observability - **Production-Ready**: Battle-tested in real-world environments across 12K+ cores for 6+ years - **Auto-discovery**: Automatically discover and monitor multiple databases within an instance - **Health Check APIs**: Comprehensive HTTP endpoints for service health and traffic routing - **Extension Support**: `timescaledb`, `citus`, `pg_stat_statements`, `pg_wait_sampling`,... - **Local-first URL behavior**: Built for on-host deployment, with implicit local target fallback and automatic `sslmode=disable` when omitted > Also support PG 9.x with [legacy config bundle](legacy/). -------- ## Quick Start RPM / DEB / Tarball available in the GitHub [release page](https://github.com/pgsty/pg_exporter/releases), and Pigsty's YUM / APT [Infra Repo](https://pigsty.io/docs/repo/infra). To run this exporter, you need to pass the postgres/pgbouncer URL via env or arg: ```bash PG_EXPORTER_URL='postgres://user:pass@host:port/postgres' pg_exporter curl http://localhost:9630/metrics # access metrics ``` There are built-in metrics such as `pg_up`, `pg_version`, `pg_in_recovery`, `pg_exporter_build_info`, and exporter self-metrics under `pg_exporter_*` (disable with `--disable-intro`). **All other metrics are defined in the [`pg_exporter.yml`](pg_exporter.yml) config file**. There are two monitoring dashboards in the [`monitor/`](monitor/) directory. You can use [**Pigsty**](https://pigsty.io) to monitor existing PostgreSQL cluster or RDS, it will setup pg_exporter for you. -------- ## Usage ```bash usage: pg_exporter [] Flags: -h, --[no-]help Show context-sensitive help (also try --help-long and --help-man). -u, --url=URL postgres target url -c, --config=CONFIG path to config dir or file --web.listen-address=:9630 ... Addresses on which to expose metrics and web interface. --web.config.file="" Path to configuration file that can enable TLS or authentication. -l, --label="" constant lables:comma separated list of label=value pair ($PG_EXPORTER_LABEL) -t, --tag="" tags,comma separated list of server tag ($PG_EXPORTER_TAG) -C, --[no-]disable-cache force not using cache ($PG_EXPORTER_DISABLE_CACHE) -m, --[no-]disable-intro disable internal/exporter self metrics ($PG_EXPORTER_DISABLE_INTRO) -a, --[no-]auto-discovery automatically scrape all database for given server ($PG_EXPORTER_AUTO_DISCOVERY) -x, --exclude-database="template0,template1,postgres" excluded databases when enabling auto-discovery ($PG_EXPORTER_EXCLUDE_DATABASE) -i, --include-database="" included databases when enabling auto-discovery ($PG_EXPORTER_INCLUDE_DATABASE) -n, --namespace="" prefix of built-in metrics, (pg|pgbouncer) by default ($PG_EXPORTER_NAMESPACE) -f, --[no-]fail-fast fail fast instead of waiting during start-up ($PG_EXPORTER_FAIL_FAST) -T, --connect-timeout=100 connect timeout in ms, 100 by default ($PG_EXPORTER_CONNECT_TIMEOUT) -P, --web.telemetry-path="/metrics" URL path under which to expose metrics. ($PG_EXPORTER_TELEMETRY_PATH) -D, --[no-]dry-run dry run and print raw configs -E, --[no-]explain explain server planned queries --log.level="info" log level: debug|info|warn|error] --log.format="logfmt" log format: logfmt|json --[no-]version Show application version. ``` Parameters could be given via command-line args or environment variables. | CLI Arg | Environment Variable | Default Value | |------------------------|--------------------------------|----------------------------------| | `--url` | `PG_EXPORTER_URL` | `postgresql:///?sslmode=disable` | | `--config` | `PG_EXPORTER_CONFIG` | `pg_exporter.yml` | | `--label` | `PG_EXPORTER_LABEL` | | | `--tag` | `PG_EXPORTER_TAG` | | | `--auto-discovery` | `PG_EXPORTER_AUTO_DISCOVERY` | `true` | | `--disable-cache` | `PG_EXPORTER_DISABLE_CACHE` | `false` | | `--fail-fast` | `PG_EXPORTER_FAIL_FAST` | `false` | | `--exclude-database` | `PG_EXPORTER_EXCLUDE_DATABASE` | | | `--include-database` | `PG_EXPORTER_INCLUDE_DATABASE` | | | `--namespace` | `PG_EXPORTER_NAMESPACE` | `pg\|pgbouncer` | | `--connect-timeout` | `PG_EXPORTER_CONNECT_TIMEOUT` | `100` | | `--dry-run` | | `false` | | `--explain` | | `false` | | `--log.level` | | `info` | | `--log.format` | | `logfmt` | | `--web.listen-address` | | `:9630` | | `--web.config.file` | | `""` | | `--web.telemetry-path` | `PG_EXPORTER_TELEMETRY_PATH` | `/metrics` | ### Connection URL Defaults - If `--url` / `PG_EXPORTER_URL` is not provided, pg_exporter falls back to a local-first default URL: `postgresql:///?sslmode=disable`. - If `sslmode` is not explicitly set in the URL, pg_exporter injects `sslmode=disable` by default. - This is an intentional design choice for common on-host deployments (`pg_exporter` and PostgreSQL/PgBouncer on the same machine), where loopback TLS adds overhead with little practical gain. - If you need TLS for remote targets, provide `sslmode` explicitly in the connection URL (for example: `sslmode=require`, `verify-ca`, `verify-full`). ------ ## API PG Exporter provides a rich set of HTTP endpoints: Here are `pg_exporter` REST APIs ```bash # Fetch metrics (customizable) curl localhost:9630/metrics # Reload configuration curl -X POST localhost:9630/reload # Explain configuration curl localhost:9630/explain # Print Statistics curl localhost:9630/stat # Aliveness health check (200 up, 503 down) curl localhost:9630/up curl localhost:9630/health curl localhost:9630/liveness curl localhost:9630/readiness # traffic route health check ### 200 if not in recovery, 404 if in recovery, 503 if server is down curl localhost:9630/primary curl localhost:9630/leader curl localhost:9630/master curl localhost:9630/read-write curl localhost:9630/rw ### 200 if in recovery, 404 if not in recovery, 503 if server is down curl localhost:9630/replica curl localhost:9630/standby curl localhost:9630/read-only curl localhost:9630/ro ### 200 if server is ready for read traffic (including primary), 503 if server is down curl localhost:9630/read ``` -------- ## Build To build a static stand-alone binary for docker scratch ```bash CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o pg_exporter ``` Or [download](https://github.com/pgsty/pg_exporter/releases) the latest prebuilt binaries from release pages. We also have pre-packaged RPM / DEB packages in the [Pigsty Infra Repo](https://pigsty.io/docs/repo/infra/) -------- ## Docker You can find pre-built amd64/arm64 docker images here: [pgsty/pg_exporter](https://hub.docker.com/r/pgsty/pg_exporter) Build a local development image from the canonical [`Dockerfile`](Dockerfile): ```bash make docker # Override the image repository when needed: make docker DOCKER_REPO=example/pg_exporter ``` Multi-architecture release images are built by [GoReleaser](.goreleaser.yml) together with the other release artifacts. -------- ## Deployment Redhat rpm and Debian/Ubuntu deb packages are made with `nfpm` for `x86/arm64`: * `/usr/bin/pg_exporter`: the pg_exporter binary. * [`/etc/pg_exporter.yml`](pg_exporter.yml): the config file * [`/usr/lib/systemd/system/pg_exporter.service`](package/pg_exporter.service): systemd service file * [`/etc/default/pg_exporter`](package/pg_exporter.default): systemd service envs & options Which is also available on Pigsty's [Infra Repo](https://pigsty.io/docs/repo/infra). ------ ## Collectors Configs lie in the core of `pg_exporter`. Actually, this project contains more lines of YAML than go. * A monolith battery-included config file: [`pg_exporter.yml`](pg_exporter.yml) * Separated metrics definition in [`config/`](config/) * Example of how to write a config file: [`doc.yml`](config/0000-doc.yml) * Authoritative Histogram design: [`docs/design/histogram.md`](docs/design/histogram.md) * Legacy config bundle for PostgreSQL 9.1 - 9.6: [`legacy/`](legacy/) ([`legacy/README.md`](legacy/README.md)) Current `pg_exporter` ships 58 collector definition files in [`config/`](config/). They are merged into [`pg_exporter.yml`](pg_exporter.yml) by `make conf`. | File | Collector namespace(s) | Scope / requirement | |-------------------------------------------------------------------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| | [0000-doc.yml](config/0000-doc.yml) | config reference | YAML schema and collector authoring guide; not an installed collector | | [0110-pg.yml](config/0110-pg.yml) | `pg` | PostgreSQL basic information, with primary and replica branches | | [0120-pg_meta.yml](config/0120-pg_meta.yml) | `pg_meta` | PostgreSQL cluster metadata; PG13+ includes `primary_conninfo` | | [0130-pg_setting.yml](config/0130-pg_setting.yml) | `pg_setting` | Shared PostgreSQL settings and capacity limits | | [0210-pg_repl.yml](config/0210-pg_repl.yml) | `pg_repl` | Replication sender statistics | | [0220-pg_sync_standby.yml](config/0220-pg_sync_standby.yml) | `pg_sync_standby` | Synchronous standby status | | [0230-pg_downstream.yml](config/0230-pg_downstream.yml) | `pg_downstream` | Downstream replication client counts | | [0240-pg_slot.yml](config/0240-pg_slot.yml) | `pg_slot` | Replication slots; PG16+ branches also work on standbys, and PG17+ recognizes the PG19 `idle_timeout` invalidation reason | | [0250-pg_recv.yml](config/0250-pg_recv.yml) | `pg_recv` | Replica WAL receiver metrics; PG13+ branch recognizes the PG19 `connecting` status | | [0260-pg_sub.yml](config/0260-pg_sub.yml) | `pg_sub` | Subscription stats; PG19 branch covers table/sequence sync errors and logical replication conflict counters | | [0270-pg_origin.yml](config/0270-pg_origin.yml) | `pg_origin` | Replication origin status; disabled by default and may need extra privileges | | [0300-pg_io.yml](config/0300-pg_io.yml) | `pg_io` | `pg_stat_io` metrics for PG16+, with PG18+ branch | | [0310-pg_size.yml](config/0310-pg_size.yml) | `pg_size` | Database, WAL, and log size metrics | | [0320-pg_archiver.yml](config/0320-pg_archiver.yml) | `pg_archiver` | Archiver process statistics | | [0330-pg_bgwriter.yml](config/0330-pg_bgwriter.yml) | `pg_bgwriter` | Background writer statistics, with PG17+ branch | | [0331-pg_checkpointer.yml](config/0331-pg_checkpointer.yml) | `pg_checkpointer` | Checkpointer statistics, with PG18+ `num_done` and `slru_written` metrics | | [0340-pg_ssl.yml](config/0340-pg_ssl.yml) | `pg_ssl` | SSL client connection counts | | [0350-pg_checkpoint.yml](config/0350-pg_checkpoint.yml) | `pg_checkpoint` | Checkpoint information from `pg_control_checkpoint` | | [0355-pg_timeline.yml](config/0355-pg_timeline.yml) | `pg_timeline` | Current timeline ID on primary or replica | | [0360-pg_recovery.yml](config/0360-pg_recovery.yml) | `pg_recovery`, `pg_recovery_prefetch` | Replica recovery and recovery-prefetch metrics | | [0370-pg_recovery_state.yml](config/0370-pg_recovery_state.yml) | `pg_recovery_state` | PG19 replica-only recovery state from `pg_stat_recovery` | | [0380-pg_slru.yml](config/0380-pg_slru.yml) | `pg_slru` | SLRU cache statistics for PG13+ | | [0390-pg_shmem.yml](config/0390-pg_shmem.yml) | `pg_shmem` | Shared memory allocation metrics; disabled by default and requires `schema:monitor` | | [0400-pg_wal.yml](config/0400-pg_wal.yml) | `pg_wal` | WAL statistics for PG14+; PG19 branch adds full-page-image byte metrics | | [0410-pg_activity.yml](config/0410-pg_activity.yml) | `pg_activity` | Backend activity counts by database and state | | [0420-pg_wait.yml](config/0420-pg_wait.yml) | `pg_wait` | Backend wait counts by wait-event type | | [0430-pg_backend.yml](config/0430-pg_backend.yml) | `pg_backend` | Backend process counts grouped by `backend_type` | | [0440-pg_xact.yml](config/0440-pg_xact.yml) | `pg_xact` | Transaction snapshot boundaries and active transaction count | | [0450-pg_xact_age.yml](config/0450-pg_xact_age.yml) | `pg_xact_age` | PG10+ cluster-wide open-transaction and idle-in-transaction age snapshot histograms per database | | [0460-pg_lock.yml](config/0460-pg_lock.yml) | `pg_lock` | Lock distribution by database and mode | | [0470-pg_lock_stat.yml](config/0470-pg_lock_stat.yml) | `pg_lock_stat` | PG19 cluster-wide lock waits, wait time, and fast-path overflow counts by lock type | | [0480-pg_query.yml](config/0480-pg_query.yml) | `pg_query` | Query metrics from `pg_stat_statements`; requires `extension:pg_stat_statements` | | [0510-pg_vacuuming.yml](config/0510-pg_vacuuming.yml) | `pg_vacuuming` | Primary-only VACUUM progress; PG18+ branch includes `delay_time` | | [0520-pg_indexing.yml](config/0520-pg_indexing.yml) | `pg_indexing` | Primary-only `CREATE INDEX` progress | | [0530-pg_clustering.yml](config/0530-pg_clustering.yml) | `pg_clustering` | Primary-only `CLUSTER` / `VACUUM FULL` progress | | [0540-pg_backup.yml](config/0540-pg_backup.yml) | `pg_backup` | Base backup progress for PG13+ | | [0610-pg_db.yml](config/0610-pg_db.yml) | `pg_db` | Database statistics; PG18+ branch includes parallel-worker launch counters | | [0620-pg_db_confl.yml](config/0620-pg_db_confl.yml) | `pg_db_confl` | Replica database conflict counters; PG16+ branch uses an explicit column list | | [0640-pg_pubrel.yml](config/0640-pg_pubrel.yml) | `pg_pubrel` | Publication and publication-relation counts | | [0650-pg_subrel.yml](config/0650-pg_subrel.yml) | `pg_subrel` | Subscription relation counts grouped by state | | [0660-pg_vacuum_score.yml](config/0660-pg_vacuum_score.yml) | `pg_vacuum_score` | PG19 current-database autovacuum score summary from `pg_stat_autovacuum_scores` | | [0700-pg_table.yml](config/0700-pg_table.yml) | `pg_table` | Per-table statistics; PG18+ branch includes vacuum/analyze time and new-page update counters | | [0710-pg_index.yml](config/0710-pg_index.yml) | `pg_index` | Per-index statistics | | [0720-pg_func.yml](config/0720-pg_func.yml) | `pg_func` | Function execution statistics | | [0730-pg_seq.yml](config/0730-pg_seq.yml) | `pg_seq` | Sequence metrics | | [0740-pg_relkind.yml](config/0740-pg_relkind.yml) | `pg_relkind` | Relation counts by relation kind | | [0750-pg_defpart.yml](config/0750-pg_defpart.yml) | `pg_defpart` | Default partition tuple counts | | [0810-pg_table_size.yml](config/0810-pg_table_size.yml) | `pg_table_size` | Per-table size metrics; can be slow on large schemas | | [0820-pg_table_bloat.yml](config/0820-pg_table_bloat.yml) | `pg_table_bloat` | Table bloat metrics; disabled by default and requires auxiliary view `pg_table_bloat` | | [0830-pg_index_bloat.yml](config/0830-pg_index_bloat.yml) | `pg_index_bloat` | B-tree index bloat metrics; disabled by default and requires auxiliary view `pg_index_bloat` | | [0910-pgbouncer_list.yml](config/0910-pgbouncer_list.yml) | `pgbouncer_list` | PgBouncer list metrics from the admin database | | [0920-pgbouncer_database.yml](config/0920-pgbouncer_database.yml) | `pgbouncer_database` | PgBouncer database metrics, with version-specific branches | | [0930-pgbouncer_stat.yml](config/0930-pgbouncer_stat.yml) | `pgbouncer_stat` | PgBouncer per-database stats, with version-specific branches | | [0940-pgbouncer_pool.yml](config/0940-pgbouncer_pool.yml) | `pgbouncer_pool` | PgBouncer pool metrics, with version-specific branches | | [1000-pg_wait_event.yml](config/1000-pg_wait_event.yml) | `pg_wait_event`, `pg_wait_event_1s` | Wait-event sampling; requires `extension:pg_wait_sampling` | | [1800-pg_tsdb_hypertable.yml](config/1800-pg_tsdb_hypertable.yml) | `pg_tsdb_hypertable` | TimescaleDB hypertable overview; disabled by default and requires `extension:timescaledb` plus `schema:timescaledb_information` | | [1900-pg_citus.yml](config/1900-pg_citus.yml) | `pg_citus_node` | Citus worker/coordinator node inventory; requires `extension:citus` | | [2000-pg_heartbeat.yml](config/2000-pg_heartbeat.yml) | `pg_heartbeat` | Monitor heartbeat table; disabled by default and requires `dbname:postgres` plus `schema:monitor` | > #### Note > > Supported version: PostgreSQL 10, 11, 12, 13, 14, 15, 16, 17, 18, 19+ > > But you can still get PostgreSQL 9.1 - 9.6 support by switching to the [`legacy/pg_exporter.yml`](legacy/pg_exporter.yml) config `pg_exporter` will generate approximately 600 metrics for a completely new database cluster. For a real-world database with 10 ~ 100 tables, it may generate several 1k ~ 10k metrics. You may need to modify or disable some database-level metrics on a database with several thousand or more tables to complete the scrape in time. Config files are using YAML format, there are lots of examples in the [config](https://github.com/pgsty/pg_exporter/tree/main/config) dir. and here is a [sample](config/0000-doc.yml) config. ``` #==============================================================# # 1. Config File #==============================================================# # The configuration file for pg_exporter is a YAML file. # Default configurations are retrieved via following precedence: # 1. command line args: --config= # 2. environment variables: PG_EXPORTER_CONFIG= # 3. pg_exporter.yml (Current directory) # 4. /etc/pg_exporter.yml (config file) # 5. /etc/pg_exporter (config dir) #==============================================================# # 2. Config Format #==============================================================# # pg_exporter config could be a single YAML file, or a directory containing a series of separated YAML files. # Each YAML config file consists of one or more metrics Collector definition, which are top-level objects. # If a directory is provided, all YAML files in that directory (non-recursive; subdirectories are ignored) will be merged in alphabetic order. # Collector definition examples are shown below. #==============================================================# # 3. Collector Example #==============================================================# # # Here is an example of a metrics collector definition # pg_primary_only: # Collector branch name. Must be UNIQUE among the entire configuration # name: pg # Collector namespace, used as METRIC PREFIX, set to branch name by default, can be override # # the same namespace may contain multiple collector branches. It`s the user`s responsibility # # to make sure that AT MOST ONE collector is picked for each namespace. # # desc: PostgreSQL basic information (on primary) # Collector description # query: | # Metrics Query SQL # # SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, # pg_current_wal_lsn() - '0/0' AS lsn, # pg_current_wal_insert_lsn() - '0/0' AS insert_lsn, # pg_current_wal_lsn() - '0/0' AS write_lsn, # pg_current_wal_flush_lsn() - '0/0' AS flush_lsn, # extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, # extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, # pg_is_in_backup() AS is_in_backup, # extract(EPOCH FROM now() - pg_backup_start_time()) AS backup_time; # # # [OPTIONAL] metadata fields, control collector behavior # ttl: 10 # Cache TTL: in seconds, how long will pg_exporter cache this collector`s query result. # timeout: 0.1 # Query Timeout: in seconds, queries that exceed this limit will be canceled. # min_version: 100000 # minimal supported version, boundary IS included. In server version number format, # max_version: 130000 # maximal supported version, boundary NOT included, In server version number format # fatal: false # Collector marked `fatal` fails, the entire scrape will abort immediately and marked as failed # skip: false # Collector marked `skip` will not be installed during the planning procedure # # tags: [cluster, primary] # Collector tags, used for planning and scheduling # # # tags are list of strings, which could be: # # * `cluster` marks this query as cluster level, so it will only execute once for the same PostgreSQL Server # # * `primary` or `master` mark this query can only run on a primary instance (WILL NOT execute if pg_is_in_recovery()) # # * `standby` or `replica` mark this query can only run on a replica instance (WILL execute if pg_is_in_recovery()) # # some special tag prefix have special interpretation: # # * `dbname:` means this query will ONLY be executed on database with name `` # # * `username:` means this query will only be executed when connect with user `` # # * `extension:` means this query will only be executed when extension `` is installed # # * `schema:` means this query will only by executed when schema `` exist # # * `not:` means this query WILL NOT be executed when exporter is tagged with `` # # * `` means this query WILL be executed when exporter is tagged with `` # # ( could not be cluster,primary,standby,master,replica,etc...) # # # One or more "predicate queries" may be defined for a metric query. These # # are run before the main metric query (after any cache hit check). If all # # of them, when run sequentially, return a single row with a single column # # boolean true result, the main metric query is executed. If any of them # # return false or return zero rows, the main query is skipped. If any # # predicate query returns more than one row, a non-boolean result, or fails # # with an error, the whole query is marked failed. Predicate queries can be # # used to check for the presence of specific functions, tables, extensions, # # settings, and vendor-specific pg features before running the main query. # # predicate_queries: # - name: predicate query name # predicate_query: | # SELECT EXISTS (SELECT 1 FROM information_schema.routines WHERE routine_schema = 'pg_catalog' AND routine_name = 'pg_backup_start_time'); # # metrics: # List of returned columns, each column must have a `name` and `usage`, `rename` and `description` are optional # - timestamp: # Column name, should be exactly the same as returned column name # usage: GAUGE # Metric type, `usage` could be # * DISCARD: completely ignoring this field # * LABEL: use columnName=columnValue as a label in metric # * GAUGE: Mark column as a gauge metric, full name will be `_` # * COUNTER: Same as above, except it is a counter rather than a gauge. # * HISTOGRAM: Aggregate one observation per SQL row into a snapshot distribution. # rename: ts # [OPTIONAL] Alias, optional, the alias will be used instead of the column name # description: xxxx # [OPTIONAL] Description of the column, will be used as a metric description # default: 0 # [OPTIONAL] Default value, will be used when column is NULL # scale: 1000 # [OPTIONAL] Scale the value by this factor # - lsn: # usage: COUNTER # description: log sequence number, current write location (on primary) # - insert_lsn: # usage: COUNTER # description: primary only, location of current wal inserting # - write_lsn: # usage: COUNTER # description: primary only, location of current wal writing # - flush_lsn: # usage: COUNTER # description: primary only, location of current wal syncing # - uptime: # usage: GAUGE # description: seconds since postmaster start # - conf_reload_time: # usage: GAUGE # description: seconds since last configuration reload # - is_in_backup: # usage: GAUGE # description: 1 if backup is in progress # - backup_time: # usage: GAUGE # description: seconds since the current backup start. null if don`t have one # # .... # you can also use rename & scale to customize the metric name and value: # - checkpoint_write_time: # rename: write_time # usage: COUNTER # scale: 1e-3 # description: Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds # - xact_age_seconds: # usage: HISTOGRAM # bucket: [1, 3, 10, 30, 100, 300, 1000, 3000] # finite increasing upper bounds # description: Open transaction age snapshot in seconds # # # HISTOGRAM is rebuilt from the current SQL result whenever the query actually executes. # # It emits cumulative `_bucket{le=...}`, `_count`, and `_sum` Gauge series, # # which may increase or decrease between snapshots. Query them directly with histogram_quantile(): # # histogram_quantile(0.95, sum by (datname, le) (pg_xact_age_seconds_bucket)) # # +Inf is automatic; the first finite bucket implicitly contains every lower value. # # Signed domains should configure finite negative boundaries and zero; do not configure -Inf. # # Do NOT apply rate(), irate(), or increase() to snapshot Histogram series. # # See docs/design/histogram.md for the authoritative contract. #==============================================================# # 4. Collector Presets #==============================================================# # pg_exporter is shipped with a series of preset collectors (already numbered and ordered by filename) # # 1xx Basic metrics: basic info, metadata, settings # 2xx Replication metrics: replication, walreceiver, downstream, sync standby, slots, subscription # 3xx Persist metrics: size, wal, background writer, checkpointer, ssl, checkpoint, recovery, slru cache, shmem usage # 4xx Activity metrics: backend count group by state, wait event, locks, xacts, queries # 5xx Progress metrics: clustering, vacuuming, indexing, basebackup, copy # 6xx Database metrics: pg_database, publication, subscription # 7xx Object metrics: pg_class, table, index, function, sequence, default partition # 8xx Optional metrics: optional metrics collector (disable by default, slow queries) # 9xx Pgbouncer metrics: metrics from pgbouncer admin database `pgbouncer` # # 100-599 Metrics for entire database cluster (scrape once) # 600-899 Metrics for single database instance (scrape for each database ,except for pg_db itself) #==============================================================# # 5. Cache TTL #==============================================================# # Cache can be used for reducing query overhead, it can be enabled by setting a non-zero value for `ttl` # It is highly recommended to use cache to avoid duplicate scrapes. Especially when you got multiple Prometheus # scraping the same instance with slow monitoring queries. Setting `ttl` to zero or leaving blank will disable # result caching, which is the default behavior # # TTL has to be smaller than your scrape interval. 15s scrape interval and 10s TTL is a good start for # production environment. Some expensive monitoring queries (such as size/bloat check) will have longer `ttl` # which can also be used as a mechanism to achieve `different scrape frequency` #==============================================================# # 6. Query Timeout #==============================================================# # Collectors can be configured with an optional Timeout. If the collector's query executes more than that # timeout, it will be canceled immediately. Setting the `timeout` to 0 or leaving blank will reset it to # default timeout 0.1 (100ms). Setting it to any negative number will disable the query timeout feature. # All queries have a default timeout of 100ms, if exceeded, the query will be canceled immediately to avoid # avalanche. You can explicitly overwrite that option. but beware: in some extreme cases, if all your # timeouts sum up greater your scrape/cache interval (usually 15s), the queries may still be jammed. # or, you can just disable potential slow queries. #==============================================================# # 7. Version Compatibility #==============================================================# # Each collector has two optional version compatibility parameters: `min_version` and `max_version`. # These two parameters specify the version compatibility of the collector. If target postgres/pgbouncer's # version is less than `min_version`, or higher than `max_version`, the collector will not be installed. # These two parameters are using PostgreSQL server version number format, which is a 6-digit integer # format as :. # For example, 090600 stands for 9.6, and 120100 stands for 12.1 # And beware that version compatibility range is left-inclusive right exclusive: [min, max), set to zero or # leaving blank will affect as -inf or +inf #==============================================================# # 8. Fatality #==============================================================# # If a collector is marked with `fatal` falls, the entire scrape operation will be marked as fail and key metrics # `pg_up` / `pgbouncer_up` will be reset to 0. It is always a good practice to set up AT LEAST ONE fatal # collector for pg_exporter. `pg.pg_primary_only` and `pgbouncer_list` are the default fatal collector. # # If a collector without `fatal` flag fails, it will increase global fail counters. But the scrape operation # will carry on. The entire scrape result will not be marked as faile, thus will not affect the `_up` metric. #==============================================================# # 9. Skip #==============================================================# # Collector with `skip` flag set to true will NOT be installed. # This could be a handy option to disable collectors #==============================================================# # 10. Tags and Planning #==============================================================# # Tags are designed for collector planning & schedule. It can be handy to customize which queries run # on which instances. And thus you can use one-single monolith config for multiple environments # # Tags are a list of strings, each string could be: # Pre-defined special tags # * `cluster` marks this collector as cluster level, so it will ONLY BE EXECUTED ONCE for the same PostgreSQL Server # * `primary` or `master` mark this collector as primary-only, so it WILL NOT work iff pg_is_in_recovery() # * `standby` or `replica` mark this collector as replica-only, so it WILL work iff pg_is_in_recovery() # Special tag prefix which have different interpretation: # * `dbname:` means this collector will ONLY work on database with name `` # * `username:` means this collector will ONLY work when connect with user `` # * `extension:` means this collector will ONLY work when extension `` is installed # * `schema:` means this collector will only work when schema `` exists # Customized positive tags (filter) and negative tags (taint) # * `not:` means this collector WILL NOT work when exporter is tagged with `` # * `` means this query WILL work if exporter is tagged with `` (special tags not included) # # pg_exporter will trigger the Planning procedure after connecting to the target. It will gather database facts # and match them with tags and other metadata (such as supported version range). Collector will only # be installed if and only if it is compatible with the target server. ``` -------------------- ## About Author: [Vonng](https://vonng.com/en) ([rh@vonng.com](mailto:rh@vonng.com)) Contributors: https://github.com/pgsty/pg_exporter/graphs/contributors License: [Apache-2.0](LICENSE) Copyright: 2018-2026 rh@vonng.com

PG Exporter Logo

pg_exporter-1.4.1/config/000077500000000000000000000000001523237665500153365ustar00rootroot00000000000000pg_exporter-1.4.1/config/0000-doc.yml000066400000000000000000000371131523237665500172100ustar00rootroot00000000000000#==============================================================# # Desc : pg_exporter metrics collector definition # Ver : PostgreSQL 10 ~ 19+ and pgbouncer 1.9~1.25+ # Ctime : 2019-12-09 # Mtime : 2026-07-11 # Homepage : https://pigsty.io/docs/pg_exporter # Author : Ruohang Feng (rh@vonng.com) # License : Apache-2.0 @ https://github.com/pgsty/pg_exporter # Copyright : 2018-2026 Ruohang Feng / Vonng (rh@vonng.com) #==============================================================# #==============================================================# # 1. Config File #==============================================================# # The configuration file for pg_exporter is a YAML file. # Default configurations are retrieved via following precedence: # 1. command line args: --config= # 2. environment variables: PG_EXPORTER_CONFIG= # 3. pg_exporter.yml (Current directory) # 4. /etc/pg_exporter.yml (config file) # 5. /etc/pg_exporter (config dir) #==============================================================# # 2. Config Format #==============================================================# # pg_exporter config could be a single YAML file, or a directory containing a series of separated YAML files. # Each YAML config file consists of one or more metrics Collector definition, which are top-level objects. # If a directory is provided, all YAML in that directory will be merged in alphabetic order. # Collector definition examples are shown below. #==============================================================# # 3. Collector Example #==============================================================# # # Here is an example of a metrics collector definition # pg_primary_only: # Collector branch name. Must be UNIQUE among the entire configuration # name: pg # Collector namespace, used as METRIC PREFIX, set to branch name by default, can be override # # the same namespace may contain multiple collector branches. It`s the user`s responsibility # # to make sure that AT MOST ONE collector is picked for each namespace. # # desc: PostgreSQL basic information (on primary) # Collector description # query: | # Metrics Query SQL # # SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, # pg_current_wal_lsn() - '0/0' AS lsn, # pg_current_wal_insert_lsn() - '0/0' AS insert_lsn, # pg_current_wal_lsn() - '0/0' AS write_lsn, # pg_current_wal_flush_lsn() - '0/0' AS flush_lsn, # extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, # extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, # pg_is_in_backup() AS is_in_backup, # extract(EPOCH FROM now() - pg_backup_start_time()) AS backup_time; # # # [OPTIONAL] metadata fields, control collector behavior # ttl: 10 # Cache TTL: in seconds, how long will pg_exporter cache this collector`s query result. # timeout: 0.1 # Query Timeout: in seconds, queries that exceed this limit will be canceled. # min_version: 100000 # minimal supported version, boundary IS included. In server version number format, # max_version: 130000 # maximal supported version, boundary NOT included, In server version number format # fatal: false # Collector marked `fatal` fails, the entire scrape will abort immediately and marked as failed # skip: false # Collector marked `skip` will not be installed during the planning procedure # # tags: [cluster, primary] # Collector tags, used for planning and scheduling # # # tags are list of strings, which could be: # # * `cluster` marks this query as cluster level, so it will only execute once for the same PostgreSQL Server # # * `primary` or `master` mark this query can only run on a primary instance (WILL NOT execute if pg_is_in_recovery()) # # * `standby` or `replica` mark this query can only run on a replica instance (WILL execute if pg_is_in_recovery()) # # some special tag prefix have special interpretation: # # * `dbname:` means this query will ONLY be executed on database with name `` # # * `username:` means this query will only be executed when connect with user `` # # * `extension:` means this query will only be executed when extension `` is installed # # * `schema:` means this query will only by executed when schema `` exist # # * `not:` means this query WILL NOT be executed when exporter is tagged with `` # # * `` means this query WILL be executed when exporter is tagged with `` # # ( could not be cluster,primary,standby,master,replica,etc...) # # # One or more "predicate queries" may be defined for a metric query. These # # are run before the main metric query (after any cache hit check). If all # # of them, when run sequentially, return a single row with a single column # # boolean true result, the main metric query is executed. If any of them # # return false or return zero rows, the main query is skipped. If any # # predicate query returns more than one row, a non-boolean result, or fails # # with an error, the whole query is marked failed. Predicate queries can be # # used to check for the presence of specific functions, tables, extensions, # # settings, and vendor-specific pg features before running the main query. # # predicate_queries: # - name: predicate query name # predicate_query: | # SELECT EXISTS (SELECT 1 FROM information_schema.routines WHERE routine_schema = 'pg_catalog' AND routine_name = 'pg_backup_start_time'); # # metrics: # List of returned columns, each column must have a `name` and `usage`, `rename` and `description` are optional # - timestamp: # Column name, should be exactly the same as returned column name # usage: GAUGE # Metric type, `usage` could be # * DISCARD: completely ignoring this field # * LABEL: use columnName=columnValue as a label in metric # * GAUGE: Mark column as a gauge metric, full name will be `_` # * COUNTER: Same as above, except it is a counter rather than a gauge. # * HISTOGRAM: Aggregate one observation per SQL row into a snapshot distribution. # rename: ts # [OPTIONAL] Alias, optional, the alias will be used instead of the column name # description: xxxx # [OPTIONAL] Description of the column, will be used as a metric description # default: 0 # [OPTIONAL] Default value, will be used when column is NULL # scale: 1000 # [OPTIONAL] Scale the value by this factor # - lsn: # usage: COUNTER # description: log sequence number, current write location (on primary) # - insert_lsn: # usage: COUNTER # description: primary only, location of current wal inserting # - write_lsn: # usage: COUNTER # description: primary only, location of current wal writing # - flush_lsn: # usage: COUNTER # description: primary only, location of current wal syncing # - uptime: # usage: GAUGE # description: seconds since postmaster start # - conf_reload_time: # usage: GAUGE # description: seconds since last configuration reload # - is_in_backup: # usage: GAUGE # description: 1 if backup is in progress # - backup_time: # usage: GAUGE # description: seconds since the current backup start. null if don`t have one # # .... # you can also use rename & scale to customize the metric name and value: # - checkpoint_write_time: # rename: write_time # usage: COUNTER # scale: 1e-3 # description: Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds # - xact_age_seconds: # usage: HISTOGRAM # bucket: [1, 3, 10, 30, 100, 300, 1000, 3000] # finite increasing upper bounds # description: Open transaction age snapshot in seconds # # # HISTOGRAM is rebuilt from the current SQL result whenever the query actually executes. # # It emits cumulative `_bucket{le=...}`, `_count`, and `_sum` Gauge series, # # which may increase or decrease between snapshots. Query them directly with histogram_quantile(): # # histogram_quantile(0.95, sum by (datname, le) (pg_xact_age_seconds_bucket)) # # +Inf is automatic; the first finite bucket implicitly contains every lower value. # # Signed domains should configure finite negative boundaries and zero; do not configure -Inf. # # Do NOT apply rate(), irate(), or increase() to snapshot Histogram series. # # See docs/design/histogram.md for the authoritative contract. #==============================================================# # 4. Collector Presets #==============================================================# # pg_exporter is shipped with a series of preset collectors (already numbered and ordered by filename) # # 1xx Basic metrics: basic info, metadata, settings # 2xx Replication metrics: replication, walreceiver, downstream, sync standby, slots, subscription # 3xx Persist metrics: size, wal, background writer, checkpointer, ssl, checkpoint, recovery, slru cache, shmem usage # 4xx Activity metrics: backend count group by state, wait event, locks, xacts, queries # 5xx Progress metrics: clustering, vacuuming, indexing, basebackup, copy # 6xx Database metrics: pg_database, publication, subscription # 7xx Object metrics: pg_class, table, index, function, sequence, default partition # 8xx Optional metrics: optional metrics collector (disable by default, slow queries) # 9xx Pgbouncer metrics: metrics from pgbouncer admin database `pgbouncer` # # 100-599 Metrics for entire database cluster (scrape once) # 600-899 Metrics for single database instance (scrape for each database ,except for pg_db itself) #==============================================================# # 5. Cache TTL #==============================================================# # Cache can be used for reducing query overhead, it can be enabled by setting a non-zero value for `ttl` # It is highly recommended to use cache to avoid duplicate scrapes. Especially when you got multiple Prometheus # scraping the same instance with slow monitoring queries. Setting `ttl` to zero or leaving blank will disable # result caching, which is the default behavior # # TTL has to be smaller than your scrape interval. 15s scrape interval and 10s TTL is a good start for # production environment. Some expensive monitoring queries (such as size/bloat check) will have longer `ttl` # which can also be used as a mechanism to achieve `different scrape frequency` #==============================================================# # 6. Query Timeout #==============================================================# # Collectors can be configured with an optional Timeout. If the collector's query executes more than that # timeout, it will be canceled immediately. Setting the `timeout` to 0 or leaving blank will reset it to # default timeout 0.1 (100ms). Setting it to any negative number will disable the query timeout feature. # All queries have a default timeout of 100ms, if exceeded, the query will be canceled immediately to avoid # avalanche. You can explicitly overwrite that option. but beware: in some extreme cases, if all your # timeouts sum up greater your scrape/cache interval (usually 15s), the queries may still be jammed. # or, you can just disable potential slow queries. #==============================================================# # 7. Version Compatibility #==============================================================# # Each collector has two optional version compatibility parameters: `min_version` and `max_version`. # These two parameters specify the version compatibility of the collector. If target postgres/pgbouncer's # version is less than `min_version`, or higher than `max_version`, the collector will not be installed. # These two parameters are using PostgreSQL server version number format, which is a 6-digit integer # format as :. # For example, 090600 stands for 9.6, and 120100 stands for 12.1 # And beware that version compatibility range is left-inclusive right exclusive: [min, max), set to zero or # leaving blank will affect as -inf or +inf #==============================================================# # 8. Fatality #==============================================================# # If a collector is marked with `fatal` falls, the entire scrape operation will be marked as fail and key metrics # `pg_up` / `pgbouncer_up` will be reset to 0. It is always a good practice to set up AT LEAST ONE fatal # collector for pg_exporter. `pg.pg_primary_only` and `pgbouncer_list` are the default fatal collector. # # If a collector without `fatal` flag fails, it will increase global fail counters. But the scrape operation # will carry on. The entire scrape result will not be marked as faile, thus will not affect the `_up` metric. #==============================================================# # 9. Skip #==============================================================# # Collector with `skip` flag set to true will NOT be installed. # This could be a handy option to disable collectors #==============================================================# # 10. Tags and Planning #==============================================================# # Tags are designed for collector planning & schedule. It can be handy to customize which queries run # on which instances. And thus you can use one-single monolith config for multiple environments # # Tags are a list of strings, each string could be: # Pre-defined special tags # * `cluster` marks this collector as cluster level, so it will ONLY BE EXECUTED ONCE for the same PostgreSQL Server # * `primary` or `master` mark this collector as primary-only, so it WILL NOT work iff pg_is_in_recovery() # * `standby` or `replica` mark this collector as replica-only, so it WILL work iff pg_is_in_recovery() # Special tag prefix which have different interpretation: # * `dbname:` means this collector will ONLY work on database with name `` # * `username:` means this collector will ONLY work when connect with user `` # * `extension:` means this collector will ONLY work when extension `` is installed # * `schema:` means this collector will only work when schema `` exists # Customized positive tags (filter) and negative tags (taint) # * `not:` means this collector WILL NOT work when exporter is tagged with `` # * `` means this query WILL work if exporter is tagged with `` (special tags not included) # # pg_exporter will trigger the Planning procedure after connecting to the target. It will gather database facts # and match them with tags and other metadata (such as supported version range). Collector will only # be installed if and only if it is compatible with the target server. pg_exporter-1.4.1/config/0110-pg.yml000066400000000000000000000134601523237665500170520ustar00rootroot00000000000000#==============================================================# # 0110 pg #==============================================================# pg_primary_only: name: pg desc: PostgreSQL basic information (on primary) query: |- SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, extract(EPOCH FROM pg_postmaster_start_time()) AS boot_time, pg_current_wal_lsn() - '0/0' AS lsn, pg_current_wal_insert_lsn() - '0/0' AS insert_lsn, pg_current_wal_lsn() - '0/0' AS write_lsn, pg_current_wal_flush_lsn() - '0/0' AS flush_lsn, NULL::BIGINT AS receive_lsn, NULL::BIGINT AS replay_lsn, extract(EPOCH FROM pg_conf_load_time()) AS reload_time, extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, NULL::FLOAT AS last_replay_time, 0::FLOAT AS lag, pg_is_in_recovery() AS is_in_recovery, FALSE AS is_wal_replay_paused; tags: [ cluster, primary ] ttl: 1 min_version: 100000 fatal: true skip: false metrics: - timestamp: { usage: GAUGE ,description: "current database timestamp in unix epoch" } - uptime: { usage: GAUGE ,description: "seconds since postmaster start" } - boot_time: { usage: GAUGE ,description: "postmaster boot timestamp in unix epoch" } - lsn: { usage: COUNTER ,description: "log sequence number, current write location" } - insert_lsn: { usage: COUNTER ,description: "primary only, location of current wal inserting" } - write_lsn: { usage: COUNTER ,description: "primary only, location of current wal writing" } - flush_lsn: { usage: COUNTER ,description: "primary only, location of current wal syncing" } - receive_lsn: { usage: COUNTER ,description: "replica only, location of wal synced to disk" } - replay_lsn: { usage: COUNTER ,description: "replica only, location of wal applied" } - reload_time: { usage: GAUGE ,description: "time when configuration was last reloaded" } - conf_reload_time: { usage: GAUGE ,description: "seconds since last configuration reload" } - last_replay_time: { usage: GAUGE ,description: "time when last transaction been replayed" } - lag: { usage: GAUGE ,description: "replica only, replication lag in seconds" } - is_in_recovery: { usage: GAUGE ,description: "1 if in recovery mode" } - is_wal_replay_paused: { usage: GAUGE ,description: "1 if wal play is paused" } pg_replica_only: name: pg desc: PostgreSQL basic information (on replica) query: |- SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, extract(EPOCH FROM pg_postmaster_start_time()) AS boot_time, pg_last_wal_replay_lsn() - '0/0' AS lsn, NULL::BIGINT AS insert_lsn, NULL::BIGINT AS write_lsn, NULL::BIGINT AS flush_lsn, pg_last_wal_receive_lsn() - '0/0' AS receive_lsn, pg_last_wal_replay_lsn() - '0/0' AS replay_lsn, extract(EPOCH FROM pg_conf_load_time()) AS reload_time, extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, extract(EPOCH FROM pg_last_xact_replay_timestamp()) AS last_replay_time, CASE WHEN pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn() THEN 0 ELSE EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()) END AS lag, pg_is_in_recovery() AS is_in_recovery, pg_is_wal_replay_paused() AS is_wal_replay_paused; tags: [ cluster, replica ] ttl: 1 min_version: 100000 fatal: true skip: false metrics: - timestamp: { usage: GAUGE ,description: "current database timestamp in unix epoch" } - uptime: { usage: GAUGE ,description: "seconds since postmaster start" } - boot_time: { usage: GAUGE ,description: "postmaster boot timestamp in unix epoch" } - lsn: { usage: COUNTER ,description: "log sequence number, current write location" } - insert_lsn: { usage: COUNTER ,description: "primary only, location of current wal inserting" } - write_lsn: { usage: COUNTER ,description: "primary only, location of current wal writing" } - flush_lsn: { usage: COUNTER ,description: "primary only, location of current wal syncing" } - receive_lsn: { usage: COUNTER ,description: "replica only, location of wal synced to disk" } - replay_lsn: { usage: COUNTER ,description: "replica only, location of wal applied" } - reload_time: { usage: GAUGE ,description: "time when configuration was last reloaded" } - conf_reload_time: { usage: GAUGE ,description: "seconds since last configuration reload" } - last_replay_time: { usage: GAUGE ,description: "time when last transaction been replayed" } - lag: { usage: GAUGE ,description: "replica only, replication lag in seconds" } - is_in_recovery: { usage: GAUGE ,description: "1 if in recovery mode" } - is_wal_replay_paused: { usage: GAUGE ,description: "1 if wal play is paused" } pg_exporter-1.4.1/config/0120-pg_meta.yml000066400000000000000000000113651523237665500200630ustar00rootroot00000000000000#==============================================================# # 0120 pg_meta #==============================================================# pg_meta_13: name: pg_meta desc: PostgreSQL meta info for pg 13+, with extra primary conninfo query: | SELECT (SELECT system_identifier FROM pg_control_system()) AS cluster_id, current_setting('cluster_name') AS cluster_name, current_setting('port') AS listen_port, current_setting('data_directory', true) AS data_dir, current_setting('config_file', true) AS conf_path, current_setting('hba_file', true) AS hba_path, current_setting('wal_level') AS wal_level, current_setting('server_encoding') AS encoding, current_setting('server_version') AS version, current_setting('server_version_num') AS ver_num, version() AS ver_str, current_setting('shared_preload_libraries', true) AS extensions, current_setting('primary_conninfo', true) AS primary_conninfo, 1 AS info ttl: 10 min_version: 130000 tags: [ cluster ] metrics: - cluster_id: { usage: LABEL ,description: "cluster system identifier" } - cluster_name: { usage: LABEL ,description: "cluster name" } - listen_port: { usage: LABEL ,description: "listen port" } - data_dir: { usage: LABEL ,description: "path to data directory" } - conf_path: { usage: LABEL ,description: "path to postgresql.conf" } - hba_path: { usage: LABEL ,description: "path to pg_hba.conf" } - wal_level: { usage: LABEL ,description: "wal level" } - encoding: { usage: LABEL ,description: "server encoding" } - version: { usage: LABEL ,description: "server version in human-readable format" } - ver_num: { usage: LABEL ,description: "server version number in machine-readable format" } - ver_str: { usage: LABEL ,description: "complete version string" } - extensions: { usage: LABEL ,description: "server installed preload libraries" } - primary_conninfo: { usage: LABEL ,description: "connection string to upstream (do not set password here)" } - info: { usage: GAUGE ,description: "constant 1" } pg_meta_10: name: pg_meta desc: PostgreSQL meta info query: | SELECT (SELECT system_identifier FROM pg_control_system()) AS cluster_id, current_setting('cluster_name') AS cluster_name, current_setting('port') AS listen_port, current_setting('data_directory', true) AS data_dir, current_setting('config_file', true) AS conf_path, current_setting('hba_file', true) AS hba_path, current_setting('wal_level') AS wal_level, current_setting('server_encoding') AS encoding, current_setting('server_version') AS version, current_setting('server_version_num') AS ver_num, version() AS ver_str, current_setting('shared_preload_libraries', true) AS extensions, 'N/A' AS primary_conninfo, 1 AS info ttl: 10 min_version: 090600 max_version: 130000 tags: [ cluster ] metrics: - cluster_id: { usage: LABEL ,description: "cluster system identifier" } - cluster_name: { usage: LABEL ,description: "cluster name" } - listen_port: { usage: LABEL ,description: "listen port" } - data_dir: { usage: LABEL ,description: "path to data directory" } - conf_path: { usage: LABEL ,description: "path to postgresql.conf" } - hba_path: { usage: LABEL ,description: "path to pg_hba.conf" } - wal_level: { usage: LABEL ,description: "wal level" } - encoding: { usage: LABEL ,description: "server encoding" } - version: { usage: LABEL ,description: "server version in human-readable format" } - ver_num: { usage: LABEL ,description: "server version number in machine-readable format" } - ver_str: { usage: LABEL ,description: "complete version string" } - extensions: { usage: LABEL ,description: "server installed preload libraries" } - primary_conninfo: { usage: LABEL ,description: "connection string to upstream (do not set password here)" } - info: { usage: GAUGE ,description: "constant 1" } pg_exporter-1.4.1/config/0130-pg_setting.yml000066400000000000000000000204561523237665500206140ustar00rootroot00000000000000#==============================================================# # 0130 pg_setting #==============================================================# # Key PostgreSQL configuration parameters # All parameters use current_setting(name, missing_ok) for version safety # Parameters introduced after PG10 use missing_ok=true to return NULL on older versions pg_setting: name: pg_setting desc: PostgreSQL shared configuration parameters (shared across all databases) query: | SELECT current_setting('max_connections')::int AS max_connections, current_setting('max_prepared_transactions')::int AS max_prepared_transactions, current_setting('max_locks_per_transaction')::int AS max_locks_per_transaction, current_setting('max_worker_processes')::int AS max_worker_processes, current_setting('max_parallel_workers')::int AS max_parallel_workers, current_setting('max_parallel_workers_per_gather')::int AS max_parallel_workers_per_gather, current_setting('max_parallel_maintenance_workers', true)::int AS max_parallel_maintenance_workers, current_setting('max_replication_slots')::int AS max_replication_slots, current_setting('max_wal_senders')::int AS max_wal_senders, current_setting('block_size')::int AS block_size, current_setting('wal_block_size')::int AS wal_block_size, pg_size_bytes(current_setting('segment_size')) AS segment_size, pg_size_bytes(current_setting('wal_segment_size')) AS wal_segment_size, CASE current_setting('data_checksums') WHEN 'on' THEN 1 ELSE 0 END AS data_checksums, CASE current_setting('wal_log_hints') WHEN 'on' THEN 1 ELSE 0 END AS wal_log_hints, CASE current_setting('fsync') WHEN 'on' THEN 1 ELSE 0 END AS fsync, CASE current_setting('full_page_writes') WHEN 'on' THEN 1 ELSE 0 END AS full_page_writes, CASE current_setting('wal_level') WHEN 'logical' THEN 3 WHEN 'replica' THEN 2 WHEN 'minimal' THEN 1 ELSE 0 END AS wal_level, pg_size_bytes(current_setting('min_wal_size')) AS min_wal_size, pg_size_bytes(current_setting('max_wal_size')) AS max_wal_size, pg_size_bytes(current_setting('max_slot_wal_keep_size', true)) AS max_slot_wal_keep_size, pg_size_bytes(current_setting('shared_buffers')) AS shared_buffers, pg_size_bytes(current_setting('work_mem')) AS work_mem, pg_size_bytes(current_setting('maintenance_work_mem')) AS maintenance_work_mem, pg_size_bytes(current_setting('effective_cache_size')) AS effective_cache_size, pg_size_bytes(current_setting('shared_memory_size', true)) AS shared_memory_size, CASE current_setting('huge_pages_status', true) WHEN 'on' THEN 1 WHEN 'off' THEN 0 WHEN 'unknown' THEN -1 ELSE NULL END AS hugepage_status, current_setting('shared_memory_size_in_huge_pages', true)::int AS hugepage_count, CASE current_setting('archive_mode') WHEN 'off' THEN 0 WHEN 'on' THEN 1 WHEN 'always' THEN 2 ELSE -1 END AS archive_mode, CASE current_setting('autovacuum') WHEN 'on' THEN 1 ELSE 0 END AS autovacuum, current_setting('autovacuum_max_workers')::int AS autovacuum_max_workers, extract(epoch from current_setting('checkpoint_timeout')::interval)::int AS checkpoint_timeout, current_setting('checkpoint_completion_target')::float AS checkpoint_completion_target, CASE current_setting('hot_standby') WHEN 'on' THEN 1 ELSE 0 END AS hot_standby, CASE current_setting('synchronous_commit') WHEN 'off' THEN 0 WHEN 'local' THEN 1 WHEN 'remote_write' THEN 2 WHEN 'on' THEN 3 WHEN 'remote_apply' THEN 4 ELSE -1 END AS synchronous_commit, CASE current_setting('io_method', true) WHEN 'sync' THEN 0 WHEN 'worker' THEN 1 WHEN 'io_uring' THEN 2 ELSE NULL END AS io_method; ttl: 10 min_version: 100000 tags: [ cluster ] metrics: - max_connections: { usage: GAUGE ,description: "maximum number of concurrent connections to the database server" } - max_prepared_transactions: { usage: GAUGE ,description: "maximum number of transactions that can be in the prepared state simultaneously" } - max_locks_per_transaction: { usage: GAUGE ,description: "maximum number of locks per transaction" } - max_worker_processes: { usage: GAUGE ,description: "maximum number of background processes" } - max_parallel_workers: { usage: GAUGE ,description: "maximum number of parallel workers that can be active at one time" } - max_parallel_workers_per_gather: { usage: GAUGE ,description: "maximum number of parallel workers per Gather node" } - max_parallel_maintenance_workers: { usage: GAUGE ,description: "maximum number of parallel maintenance workers (PG11+, NULL on older)" } - max_replication_slots: { usage: GAUGE ,description: "maximum number of replication slots" } - max_wal_senders: { usage: GAUGE ,description: "maximum number of concurrent WAL sender connections" } - block_size: { usage: GAUGE ,description: "database block size in bytes (default 8192)" } - wal_block_size: { usage: GAUGE ,description: "WAL block size in bytes" } - segment_size: { usage: GAUGE ,description: "database file segment size in bytes" } - wal_segment_size: { usage: GAUGE ,description: "WAL segment size in bytes" } - data_checksums: { usage: GAUGE ,description: "data checksums enabled, 1=on 0=off" } - wal_log_hints: { usage: GAUGE ,description: "WAL log hints enabled, 1=on 0=off" } - fsync: { usage: GAUGE ,description: "fsync enabled (CRITICAL for data safety), 1=on 0=off" } - full_page_writes: { usage: GAUGE ,description: "full page writes enabled, 1=on 0=off" } - wal_level: { usage: GAUGE ,description: "WAL level, 1=minimal 2=replica 3=logical" } - min_wal_size: { usage: GAUGE ,description: "minimum WAL size in bytes" } - max_wal_size: { usage: GAUGE ,description: "maximum WAL size in bytes" } - max_slot_wal_keep_size: { usage: GAUGE ,description: "maximum WAL size retained by replication slots in bytes (PG13+, NULL on older)" } - shared_buffers: { usage: GAUGE ,description: "shared buffer size in bytes" } - work_mem: { usage: GAUGE ,description: "work memory size in bytes" } - maintenance_work_mem: { usage: GAUGE ,description: "maintenance work memory size in bytes" } - effective_cache_size: { usage: GAUGE ,description: "planner's assumption about effective OS cache size in bytes" } - shared_memory_size: { usage: GAUGE ,description: "total shared memory size in bytes (PG13+, NULL on older)" } - hugepage_status: { usage: GAUGE ,description: "huge pages status, 1=on 0=off -1=unknown NULL=unavailable (PG14+)" } - hugepage_count: { usage: GAUGE ,description: "number of huge pages needed for shared memory (PG14+, NULL on older)" } - archive_mode: { usage: GAUGE ,description: "archive mode, 0=off 1=on 2=always" } - autovacuum: { usage: GAUGE ,description: "autovacuum enabled, 1=on 0=off" } - autovacuum_max_workers: { usage: GAUGE ,description: "maximum number of autovacuum worker processes" } - checkpoint_timeout: { usage: GAUGE ,description: "checkpoint timeout in seconds" } - checkpoint_completion_target: { usage: GAUGE ,description: "checkpoint completion target (0.0-1.0)" } - hot_standby: { usage: GAUGE ,description: "hot standby mode enabled, 1=on 0=off" } - synchronous_commit: { usage: GAUGE ,description: "synchronous commit level, 0=off 1=local 2=remote_write 3=on 4=remote_apply" } - io_method: { usage: GAUGE ,description: "I/O method (PG18+), 0=sync 1=worker 2=io_uring NULL=unavailable" } pg_exporter-1.4.1/config/0210-pg_repl.yml000066400000000000000000000224051523237665500200740ustar00rootroot00000000000000#==============================================================# # 0210 pg_repl #==============================================================# pg_repl_12: name: pg_repl desc: PostgreSQL replication stat metrics 12+ query: | SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, pid::TEXT, client_port, CASE state WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, CASE sync_state WHEN 'async' THEN 0 WHEN 'potential' THEN 1 WHEN 'sync' THEN 2 WHEN 'quorum' THEN 3 ELSE -1 END AS sync_state, sync_priority, backend_xmin::TEXT::BIGINT AS backend_xmin, current.lsn - '0/0' AS lsn, current.lsn - sent_lsn AS sent_diff, current.lsn - write_lsn AS write_diff, current.lsn - flush_lsn AS flush_diff, current.lsn - replay_lsn AS replay_diff, sent_lsn - '0/0' AS sent_lsn, write_lsn - '0/0' AS write_lsn, flush_lsn - '0/0' AS flush_lsn, replay_lsn - '0/0' AS replay_lsn, coalesce(extract(EPOCH FROM write_lag), 0) AS write_lag, coalesce(extract(EPOCH FROM flush_lag), 0) AS flush_lag, coalesce(extract(EPOCH FROM replay_lag), 0) AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time, extract(EPOCH FROM reply_time) AS reply_time FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END AS lsn) current; ttl: 10 min_version: 120000 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback." } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } - reply_time: { usage: GAUGE ,description: "Send time of last reply message received from standby server" } pg_repl_10: name: pg_repl desc: PostgreSQL replication stat metrics v10 v11 query: | SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, pid::TEXT, client_port, CASE state WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, CASE sync_state WHEN 'async' THEN 0 WHEN 'potential' THEN 1 WHEN 'sync' THEN 2 WHEN 'quorum' THEN 3 ELSE -1 END AS sync_state, sync_priority, backend_xmin::TEXT::BIGINT AS backend_xmin, current.lsn - '0/0' AS lsn, current.lsn - sent_lsn AS sent_diff, current.lsn - write_lsn AS write_diff, current.lsn - flush_lsn AS flush_diff, current.lsn - replay_lsn AS replay_diff, sent_lsn - '0/0' AS sent_lsn, write_lsn - '0/0' AS write_lsn, flush_lsn - '0/0' AS flush_lsn, replay_lsn - '0/0' AS replay_lsn, coalesce(extract(EPOCH FROM write_lag), 0) AS write_lag, coalesce(extract(EPOCH FROM flush_lag), 0) AS flush_lag, coalesce(extract(EPOCH FROM replay_lag), 0) AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END AS lsn) current; ttl: 10 min_version: 100000 max_version: 120000 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback." } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } pg_exporter-1.4.1/config/0220-pg_sync_standby.yml000066400000000000000000000014141523237665500216300ustar00rootroot00000000000000#==============================================================# # 0220 pg_sync_standby #==============================================================# pg_sync_standby: name: pg_sync_standby desc: PostgreSQL synchronous standby status and names query: | SELECT CASE WHEN names <> '' THEN names ELSE '' END AS names, CASE WHEN names <> '' THEN 1 ELSE 0 END AS enabled FROM (SELECT current_setting('synchronous_standby_names') AS names) n; ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - names: { usage: LABEL ,description: "List of standby servers that can support synchronous replication, if not enabled" } - enabled: { usage: GAUGE ,description: "Synchronous commit enabled, 1 if enabled, 0 if disabled" } pg_exporter-1.4.1/config/0230-pg_downstream.yml000066400000000000000000000014311523237665500213130ustar00rootroot00000000000000#==============================================================# # 0230 pg_downstream #==============================================================# pg_downstream: name: pg_downstream desc: PostgreSQL replication client count group by state query: | SELECT l.state, coalesce(count, 0 ) AS count FROM unnest(ARRAY ['streaming','startup','catchup', 'backup', 'stopping']) l(state) LEFT JOIN (SELECT state, count(*) AS count FROM pg_stat_replication GROUP BY state)r ON l.state = r.state; ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - state: { usage: LABEL ,description: "Replication client state, could be one of startup|catchup|streaming|backup|stopping" } - count: { usage: GAUGE ,description: "Count of corresponding state" } pg_exporter-1.4.1/config/0240-pg_slot.yml000066400000000000000000000463651523237665500201310ustar00rootroot00000000000000#==============================================================# # 0240 pg_slot #==============================================================# pg_slot_17: name: pg_slot desc: PostgreSQL replication slot metrics v17, slot also exists on standby query: |- SELECT s.slot_name, s.slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,two_phase,conflicting,failover,synced, xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes, safe_wal_size, CASE wal_status WHEN 'reserved' THEN 0 WHEN 'extended' THEN 1 WHEN 'unreserved' THEN 2 WHEN 'lost' THEN 3 ELSE -1 END AS wal_status, spill_txns,spill_count,spill_bytes,stream_txns,stream_count,stream_bytes,total_txns,total_bytes,extract(EPOCH FROM stats_reset) AS reset_time, extract(EPOCH FROM inactive_since) AS inactive_since, CASE invalidation_reason WHEN 'wal_removed' THEN 1 WHEN 'rows_removed' THEN 2 WHEN 'wal_level_insufficient' THEN 3 WHEN 'idle_timeout' THEN 4 ELSE 0 END AS invalidation_reason FROM pg_replication_slots s LEFT OUTER JOIN pg_stat_replication_slots ss ON s.slot_name = ss.slot_name; ttl: 10 min_version: 170000 tags: [ cluster ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - two_phase: { usage: GAUGE ,description: "True(1) if the slot is enabled for decoding prepared transactions. Always false for physical slots." } - conflicting: { usage: GAUGE ,description: "True(1) if this logical slot conflicted with recovery. Always NULL for physical slots." } - failover: { usage: GAUGE ,description: "True(1) if this is a logical slot enabled to be synced to the standbys" } - synced: { usage: GAUGE ,description: "True(1) if this is a logical slot that was synced from a primary server" } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } - safe_wal_size: { usage: GAUGE ,description: "bytes that can be written to WAL which will not make slot into lost" } - wal_status: { usage: GAUGE ,description: "WAL reserve status 0-3 means reserved,extended,unreserved,lost, -1 means other" } - spill_txns: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding (subtrans included)" } - spill_count: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding" } - spill_bytes: { usage: COUNTER ,description: "Bytes that spilled to disk due to logical decode mem exceeding" } - stream_txns: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_count: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_bytes: { usage: COUNTER ,description: "Bytes that streamed to decoding output plugin after mem exceed" } - total_txns: { usage: COUNTER ,description: "Number of decoded xacts sent to the decoding output plugin for this slot" } - total_bytes: { usage: COUNTER ,description: "Number of decoded bytes sent to the decoding output plugin for this slot" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } - invalidation_reason: { usage: GAUGE ,description: "ok=0, wal_removed=1, rows_removed=2, wal_level_insufficient=3, idle_timeout=4" } - inactive_since: { usage: GAUGE ,description: "The time when the slot became inactive" } pg_slot_16: name: pg_slot desc: PostgreSQL replication slot metrics v16 with conflicting, now slot also exists on standby query: |- SELECT s.slot_name, s.slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,two_phase,conflicting,xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes, safe_wal_size, CASE wal_status WHEN 'reserved' THEN 0 WHEN 'extended' THEN 1 WHEN 'unreserved' THEN 2 WHEN 'lost' THEN 3 ELSE -1 END AS wal_status, spill_txns,spill_count,spill_bytes,stream_txns,stream_count,stream_bytes,total_txns,total_bytes,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_replication_slots s LEFT OUTER JOIN pg_stat_replication_slots ss ON s.slot_name = ss.slot_name; ttl: 10 min_version: 160000 max_version: 170000 tags: [ cluster ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - two_phase: { usage: GAUGE ,description: "True(1) if the slot is enabled for decoding prepared transactions. Always false for physical slots." } - conflicting: { usage: GAUGE ,description: "True if this logical slot conflicted with recovery. Always NULL for physical slots." } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } - safe_wal_size: { usage: GAUGE ,description: "bytes that can be written to WAL which will not make slot into lost" } - wal_status: { usage: GAUGE ,description: "WAL reserve status 0-3 means reserved,extended,unreserved,lost, -1 means other" } - spill_txns: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding (subtrans included)" } - spill_count: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding" } - spill_bytes: { usage: COUNTER ,description: "Bytes that spilled to disk due to logical decode mem exceeding" } - stream_txns: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_count: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_bytes: { usage: COUNTER ,description: "Bytes that streamed to decoding output plugin after mem exceed" } - total_txns: { usage: COUNTER ,description: "Number of decoded xacts sent to the decoding output plugin for this slot" } - total_bytes: { usage: COUNTER ,description: "Number of decoded bytes sent to the decoding output plugin for this slot" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } pg_slot_14: name: pg_slot desc: PostgreSQL replication slot metrics v14 with pg_stat_replication_slots metrics query: |- SELECT s.slot_name, s.slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,two_phase,xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes, safe_wal_size, CASE wal_status WHEN 'reserved' THEN 0 WHEN 'extended' THEN 1 WHEN 'unreserved' THEN 2 WHEN 'lost' THEN 3 ELSE -1 END AS wal_status, spill_txns,spill_count,spill_bytes,stream_txns,stream_count,stream_bytes,total_txns,total_bytes,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_replication_slots s LEFT OUTER JOIN pg_stat_replication_slots ss ON s.slot_name = ss.slot_name; ttl: 10 min_version: 140000 max_version: 160000 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - two_phase: { usage: GAUGE ,description: "True(1) if the slot is enabled for decoding prepared transactions. Always false for physical slots." } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } - safe_wal_size: { usage: GAUGE ,description: "bytes that can be written to WAL which will not make slot into lost" } - wal_status: { usage: GAUGE ,description: "WAL reserve status 0-3 means reserved,extended,unreserved,lost, -1 means other" } - spill_txns: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding (subtrans included)" } - spill_count: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding" } - spill_bytes: { usage: COUNTER ,description: "Bytes that spilled to disk due to logical decode mem exceeding" } - stream_txns: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_count: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_bytes: { usage: COUNTER ,description: "Bytes that streamed to decoding output plugin after mem exceed" } - total_txns: { usage: COUNTER ,description: "Number of decoded xacts sent to the decoding output plugin for this slot" } - total_bytes: { usage: COUNTER ,description: "Number of decoded bytes sent to the decoding output plugin for this slot" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } pg_slot_13: name: pg_slot desc: PostgreSQL replication slot metrics v13 (wal safe size and status) query: |- SELECT slot_name, slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes, safe_wal_size, CASE wal_status WHEN 'reserved' THEN 0 WHEN 'extended' THEN 1 WHEN 'unreserved' THEN 2 WHEN 'lost' THEN 3 ELSE -1 END AS wal_status FROM pg_replication_slots; ttl: 10 min_version: 130000 max_version: 140000 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } - safe_wal_size: { usage: GAUGE ,description: "bytes that can be written to WAL which will not make slot into lost" } - wal_status: { usage: GAUGE ,description: "WAL reserve status 0-3 means reserved,extended,unreserved,lost, -1 means other" } pg_slot_10: name: pg_slot desc: PostgreSQL replication slot metrics 10 ~ 12 query: |- SELECT slot_name, slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes FROM pg_replication_slots; ttl: 10 min_version: 100000 max_version: 130000 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } pg_exporter-1.4.1/config/0250-pg_recv.yml000066400000000000000000000200421523237665500200700ustar00rootroot00000000000000#==============================================================# # 0250 pg_recv #==============================================================# pg_recv_13: name: pg_recv desc: PostgreSQL walreceiver metrics 13+ query: |- SELECT coalesce(sender_host, (regexp_match(conninfo, '.*host=(\S+).*'))[1]) AS sender_host, coalesce(sender_port::TEXT, (regexp_match(conninfo, '.*port=(\S+).*'))[1]) AS sender_port, coalesce(slot_name, 'NULL') AS slot_name, pid, CASE status WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 WHEN 'connecting' THEN 5 ELSE -1 END AS state, receive_start_lsn - '0/0' AS init_lsn,receive_start_tli AS init_tli, flushed_lsn - '0/0' AS flush_lsn,written_lsn - '0/0' AS write_lsn, received_tli AS flush_tli, latest_end_lsn - '0/0' AS reported_lsn, last_msg_send_time AS msg_send_time,last_msg_receipt_time AS msg_recv_time,latest_end_time AS reported_time,now() AS time FROM pg_stat_wal_receiver; ttl: 10 min_version: 130000 tags: [ cluster, replica ] metrics: - sender_host: { usage: LABEL ,description: "Host of the PostgreSQL instance this WAL receiver is connected to" } - sender_port: { usage: LABEL ,description: "Port number of the PostgreSQL instance this WAL receiver is connected to." } - slot_name: { usage: LABEL ,description: "Replication slot name used by this WAL receiver" } - pid: { usage: GAUGE ,description: "Process ID of the WAL receiver process" } - state: { usage: GAUGE ,description: "Encoded activity status of the WAL receiver process 0-5 for streaming|startup|catchup|backup|stopping|connecting" } - init_lsn: { usage: COUNTER ,description: "First write-ahead log location used when WAL receiver is started" } - init_tli: { usage: COUNTER ,description: "First timeline number used when WAL receiver is started" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and flushed to disk" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and written to disk, but not flushed." } - flush_tli: { usage: COUNTER ,description: "Timeline number of last write-ahead log location received and flushed to disk" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - time: { usage: GAUGE ,description: "Time of current snapshot" } pg_recv_11: name: pg_recv desc: PostgreSQL walreceiver metrics (11-12) query: |- SELECT coalesce(sender_host, (regexp_match(conninfo, '.*host=(\S+).*'))[1]) AS sender_host, coalesce(sender_port::TEXT, (regexp_match(conninfo, '.*port=(\S+).*'))[1]) AS sender_port, coalesce(slot_name, 'NULL') AS slot_name, pid, CASE status WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, receive_start_lsn - '0/0' AS init_lsn,receive_start_tli AS init_tli, received_lsn - '0/0' AS flush_lsn, received_tli AS flush_tli, latest_end_lsn - '0/0' AS reported_lsn, last_msg_send_time AS msg_send_time,last_msg_receipt_time AS msg_recv_time,latest_end_time AS reported_time,now() AS time FROM pg_stat_wal_receiver; ttl: 10 tags: [ cluster, replica ] min_version: 110000 max_version: 130000 metrics: - sender_host: { usage: LABEL ,description: "Host of the PostgreSQL instance this WAL receiver is connected to" } - sender_port: { usage: LABEL ,description: "Port number of the PostgreSQL instance this WAL receiver is connected to." } - slot_name: { usage: LABEL ,description: "Replication slot name used by this WAL receiver" } - pid: { usage: GAUGE ,description: "Process ID of the WAL receiver process" } - state: { usage: GAUGE ,description: "Encoded activity status of the WAL receiver process 0-4 for streaming|startup|catchup|backup|stopping" } - init_lsn: { usage: COUNTER ,description: "First write-ahead log location used when WAL receiver is started" } - init_tli: { usage: COUNTER ,description: "First timeline number used when WAL receiver is started" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and flushed to disk" } - flush_tli: { usage: COUNTER ,description: "Timeline number of last write-ahead log location received and flushed to disk" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - time: { usage: GAUGE ,description: "Time of current snapshot" } pg_recv_10: name: pg_recv desc: PostgreSQL walreceiver metrics (10) query: |- SELECT (regexp_match(conninfo, '.*host=(\S+).*'))[1] AS sender_host, (regexp_match(conninfo, '.*port=(\S+).*'))[1] AS sender_port, coalesce(slot_name, 'NULL') AS slot_name, pid, CASE status WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, receive_start_lsn - '0/0' AS init_lsn,receive_start_tli AS init_tli, received_lsn - '0/0' AS flush_lsn, received_tli AS flush_tli, latest_end_lsn - '0/0' AS reported_lsn, last_msg_send_time AS msg_send_time,last_msg_receipt_time AS msg_recv_time,latest_end_time AS reported_time,now() AS time FROM pg_stat_wal_receiver; ttl: 10 tags: [ cluster, replica ] min_version: 100000 max_version: 110000 metrics: - sender_host: { usage: LABEL ,description: "Host of the PostgreSQL instance this WAL receiver is connected to" } - sender_port: { usage: LABEL ,description: "Port number of the PostgreSQL instance this WAL receiver is connected to." } - slot_name: { usage: LABEL ,description: "Replication slot name used by this WAL receiver" } - pid: { usage: GAUGE ,description: "Process ID of the WAL receiver process" } - state: { usage: GAUGE ,description: "Encoded activity status of the WAL receiver process 0-4 for streaming|startup|catchup|backup|stopping" } - init_lsn: { usage: COUNTER ,description: "First write-ahead log location used when WAL receiver is started" } - init_tli: { usage: COUNTER ,description: "First timeline number used when WAL receiver is started" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and flushed to disk" } - flush_tli: { usage: COUNTER ,description: "Timeline number of last write-ahead log location received and flushed to disk" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - time: { usage: GAUGE ,description: "Time of current snapshot" } pg_exporter-1.4.1/config/0260-pg_sub.yml000066400000000000000000000236431523237665500177350ustar00rootroot00000000000000#==============================================================# # 0260 pg_sub #==============================================================# pg_sub_19: name: pg_sub desc: PostgreSQL subscription statistics (19+) query: |- SELECT s2.subname, s2.subid AS id, s1.pid, s1.received_lsn, s1.reported_lsn, s1.msg_send_time, s1.msg_recv_time, s1.reported_time, (s1.pid IS NOT NULL)::int AS has_worker, coalesce(s2.apply_error_count, 0) AS apply_error_count, coalesce(s2.sync_table_error_count, 0) AS sync_table_error_count, coalesce(s2.sync_seq_error_count, 0) AS sync_seq_error_count, coalesce(s2.sync_table_error_count, 0) + coalesce(s2.sync_seq_error_count, 0) AS sync_error_count, coalesce(s2.confl_insert_exists, 0) AS confl_insert_exists, coalesce(s2.confl_update_origin_differs, 0) AS confl_update_origin_differs, coalesce(s2.confl_update_exists, 0) AS confl_update_exists, coalesce(s2.confl_update_deleted, 0) AS confl_update_deleted, coalesce(s2.confl_update_missing, 0) AS confl_update_missing, coalesce(s2.confl_delete_origin_differs, 0) AS confl_delete_origin_differs, coalesce(s2.confl_delete_missing, 0) AS confl_delete_missing, coalesce(s2.confl_multiple_unique_conflicts, 0) AS confl_multiple_unique_conflicts, extract(EPOCH FROM s2.stats_reset) AS reset_time FROM pg_stat_subscription_stats s2 LEFT OUTER JOIN (SELECT subname, subid, pid, received_lsn - '0/0' AS received_lsn, latest_end_lsn - '0/0' AS reported_lsn, extract(epoch from last_msg_send_time) AS msg_send_time, extract(epoch from last_msg_receipt_time) AS msg_recv_time, extract(epoch from latest_end_time) AS reported_time FROM pg_stat_subscription WHERE relid IS NULL AND leader_pid IS NULL AND coalesce(worker_type, 'apply') = 'apply') s1 USING(subid); ttl: 10 min_version: 190000 tags: [ cluster ] metrics: - subname: { usage: LABEL ,description: "Name of this subscription" } - id: { usage: GAUGE ,description: "OID of the subscription" } - pid: { usage: GAUGE ,description: "Process ID of the subscription leader apply worker" } - has_worker: { usage: GAUGE ,description: "1 if a leader apply worker row exists in pg_stat_subscription, otherwise 0" } - received_lsn: { usage: COUNTER ,description: "Last write-ahead log location received" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - apply_error_count: { usage: COUNTER ,description: "Number of times an error occurred while applying changes" } - sync_table_error_count: { usage: COUNTER ,description: "Number of times an error occurred during the initial table synchronization" } - sync_seq_error_count: { usage: COUNTER ,description: "Number of times an error occurred during sequence synchronization" } - sync_error_count: { usage: COUNTER ,description: "Sum of sync_table_error_count and sync_seq_error_count. Drop-in replacement for the PG16-18 sync_error_count metric; on PG19+ it also includes sequence-sync errors" } - confl_insert_exists: { usage: COUNTER ,description: "Number of insert_exists conflicts while applying changes" } - confl_update_origin_differs: { usage: COUNTER ,description: "Number of update_origin_differs conflicts while applying changes" } - confl_update_exists: { usage: COUNTER ,description: "Number of update_exists conflicts while applying changes" } - confl_update_deleted: { usage: COUNTER ,description: "Number of update_deleted conflicts while applying changes" } - confl_update_missing: { usage: COUNTER ,description: "Number of update_missing conflicts while applying changes" } - confl_delete_origin_differs: { usage: COUNTER ,description: "Number of delete_origin_differs conflicts while applying changes" } - confl_delete_missing: { usage: COUNTER ,description: "Number of delete_missing conflicts while applying changes" } - confl_multiple_unique_conflicts: { usage: COUNTER ,description: "Number of multiple_unique_conflicts conflicts while applying changes" } - reset_time: { usage: GAUGE ,description: "Time at which these subscription statistics were last reset" } pg_sub_16: name: pg_sub desc: PostgreSQL subscription statistics (16-18) query: |- SELECT s1.subname, subid AS id, pid, received_lsn, reported_lsn, msg_send_time, msg_recv_time, reported_time, apply_error_count, sync_error_count FROM (SELECT subname, subid, pid, received_lsn - '0/0' AS received_lsn, latest_end_lsn - '0/0' AS reported_lsn, extract(epoch from last_msg_send_time) AS msg_send_time, extract(epoch from last_msg_receipt_time) AS msg_recv_time, extract(epoch from latest_end_time) AS reported_time FROM pg_stat_subscription WHERE relid IS NULL AND leader_pid IS NULL) s1 LEFT OUTER JOIN pg_stat_subscription_stats s2 USING(subid); ttl: 10 min_version: 160000 max_version: 190000 tags: [ cluster ] metrics: - subname: { usage: LABEL ,description: "Name of this subscription" } - id: { usage: GAUGE ,description: "OID of the subscription" } - pid: { usage: GAUGE ,description: "Process ID of the subscription leader apply worker" } - received_lsn: { usage: COUNTER ,description: "Last write-ahead log location received" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - apply_error_count: { usage: COUNTER ,description: "Number of times an error occurred while applying changes" } - sync_error_count: { usage: COUNTER ,description: "Number of times an error occurred during the initial table synchronization" } pg_sub_15: name: pg_sub desc: PostgreSQL subscription statistics (15) query: |- SELECT s1.subname, subid AS id, pid, received_lsn, reported_lsn, msg_send_time, msg_recv_time, reported_time, apply_error_count, sync_error_count FROM (SELECT subname, subid, pid, received_lsn - '0/0' AS received_lsn, latest_end_lsn - '0/0' AS reported_lsn, extract(epoch from last_msg_send_time) AS msg_send_time, extract(epoch from last_msg_receipt_time) AS msg_recv_time, extract(epoch from latest_end_time) AS reported_time FROM pg_stat_subscription WHERE relid ISNULL) s1 LEFT OUTER JOIN pg_stat_subscription_stats s2 USING(subid); ttl: 10 min_version: 150000 max_version: 160000 tags: [ cluster ] metrics: - subname: { usage: LABEL ,description: "Name of this subscription" } - id: { usage: GAUGE ,description: "OID of the subscription" } - pid: { usage: GAUGE ,description: "Process ID of the subscription main apply worker process" } - received_lsn: { usage: COUNTER ,description: "Last write-ahead log location received" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - apply_error_count: { usage: COUNTER ,description: "Number of times an error occurred while applying changes." } - sync_error_count: { usage: COUNTER ,description: "Number of times an error occurred during the initial table synchronization" } pg_sub_10: name: pg_sub desc: PostgreSQL subscription statistics (10-14) query: |- SELECT subname, subid AS id, pid, received_lsn - '0/0' AS received_lsn, latest_end_lsn - '0/0' AS reported_lsn, extract(epoch from last_msg_send_time) AS msg_send_time, extract(epoch from last_msg_receipt_time) AS msg_recv_time, extract(epoch from latest_end_time) AS reported_time FROM pg_stat_subscription WHERE relid ISNULL; ttl: 10 min_version: 100000 max_version: 150000 tags: [ cluster ] metrics: - subname: { usage: LABEL ,description: "Name of this subscription" } - id: { usage: GAUGE ,description: "OID of the subscription" } - pid: { usage: GAUGE ,description: "Process ID of the subscription main apply worker process" } - received_lsn: { usage: COUNTER ,description: "Last write-ahead log location received" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } pg_exporter-1.4.1/config/0270-pg_origin.yml000066400000000000000000000017671523237665500204370ustar00rootroot00000000000000#==============================================================# # 0270 pg_origin #==============================================================# # skip by default, require additional privilege setup # GRANT SELECT ON pg_replication_origin, pg_replication_origin_status TO pg_monitor; pg_origin: name: pg_origin desc: PostgreSQL replay state (approximate) for a certain origin query: SELECT roname, remote_lsn - '0/0' AS remote_lsn, local_lsn - '0/0' AS local_lsn FROM pg_replication_origin o LEFT JOIN pg_replication_origin_status os ON o.roident = os.local_id; ttl: 10 min_version: 090500 skip: true tags: [ cluster ] metrics: - roname: { usage: LABEL ,description: "The external, user defined, name of a replication origin." } - remote_lsn: { usage: COUNTER ,description: "The origin node's LSN up to which data has been replicated." } - local_lsn: { usage: COUNTER ,description: "This node's LSN at which remote_lsn has been replicated." } pg_exporter-1.4.1/config/0300-pg_io.yml000066400000000000000000000141421523237665500175400ustar00rootroot00000000000000#==============================================================# # 0300 pg_io #==============================================================# pg_io_18: name: pg_io desc: PostgreSQL I/O stats since v18 query: |- SELECT backend_type AS "type",object,context,reads,read_bytes,read_time,writes,write_bytes,write_time,writebacks,writeback_time, extends,extend_bytes,extend_time,hits,evictions,reuses,fsyncs,fsync_time,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_io; ttl: 10 timeout: 1 min_version: 180000 tags: [ cluster ] metrics: - type: { usage: LABEL ,description: "Type of backend" } - object: { usage: LABEL ,description: "Target object of an I/O operation, relation or temp" } - context: { usage: LABEL ,description: "The context of an I/O operation. normal,vacuum,bulkread,bulkwrite" } - reads: { usage: COUNTER ,default: 0 ,description: "Number of read operations, each of the size specified in op_bytes." } - read_bytes: { usage: COUNTER ,default: 0 ,description: "Number of read bytes" } - read_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in read operations in seconds" } - writes: { usage: COUNTER ,default: 0 ,description: "Number of write operations, each of the size specified in op_bytes." } - write_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in write operations in seconds" } - write_bytes: { usage: COUNTER ,default: 0 ,description: "Number of write bytes" } - writebacks: { usage: COUNTER ,default: 0 ,description: "Number of units of size op_bytes which the process requested the kernel write out to permanent storage." } - writeback_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in writeback operations in seconds" } - extends: { usage: COUNTER ,default: 0 ,description: "Number of relation extend operations, each of the size specified in op_bytes." } - extend_bytes: { usage: COUNTER ,default: 0 ,description: "Number of extend bytes" } - extend_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in extend operations in seconds" } - hits: { usage: COUNTER ,default: 0 ,description: "The number of times a desired block was found in a shared buffer." } - evictions: { usage: COUNTER ,default: 0 ,description: "Number of times a block has been written out from a shared or local buffer" } - reuses: { usage: COUNTER ,default: 0 ,description: "The number of times an existing buffer is reused" } - fsyncs: { usage: COUNTER ,default: 0 ,description: "Number of fsync calls. These are only tracked in context normal" } - fsync_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in fsync operations in seconds" } - reset_time: { usage: GAUGE ,description: "Timestamp at which these statistics were last reset" } pg_io_16: name: pg_io desc: PostgreSQL I/O stats query: |- SELECT backend_type AS "type", object, context, reads, read_time,writes,write_time,writebacks,writeback_time,extends, extend_time,hits,evictions,reuses,fsyncs,fsync_time,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_io; ttl: 10 timeout: 1 min_version: 160000 max_version: 180000 tags: [ cluster ] metrics: - type: { usage: LABEL ,description: "Type of backend" } - object: { usage: LABEL ,description: "Target object of an I/O operation, relation or temp" } - context: { usage: LABEL ,description: "The context of an I/O operation. normal,vacuum,bulkread,bulkwrite" } - reads: { usage: COUNTER ,default: 0 ,description: "Number of read operations, each of the size specified in op_bytes." } - read_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in read operations in seconds" } - writes: { usage: COUNTER ,default: 0 ,description: "Number of write operations, each of the size specified in op_bytes." } - write_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in write operations in seconds" } - writebacks: { usage: COUNTER ,default: 0 ,description: "Number of units of size op_bytes which the process requested the kernel write out to permanent storage." } - writeback_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in writeback operations in seconds" } - extends: { usage: COUNTER ,default: 0 ,description: "Number of relation extend operations, each of the size specified in op_bytes." } - extend_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in extend operations in seconds" } - hits: { usage: COUNTER ,default: 0 ,description: "The number of times a desired block was found in a shared buffer." } - evictions: { usage: COUNTER ,default: 0 ,description: "Number of times a block has been written out from a shared or local buffer" } - reuses: { usage: COUNTER ,default: 0 ,description: "The number of times an existing buffer is reused" } - fsyncs: { usage: COUNTER ,default: 0 ,description: "Number of fsync calls. These are only tracked in context normal" } - fsync_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in fsync operations in seconds" } - reset_time: { usage: GAUGE ,description: "Timestamp at which these statistics were last reset" } pg_exporter-1.4.1/config/0310-pg_size.yml000066400000000000000000000014711523237665500201050ustar00rootroot00000000000000#==============================================================# # 0310 pg_size #==============================================================# pg_size: name: pg_size desc: PostgreSQL Database, WAL, Log size since v10 query: |- SELECT datname, pg_database_size(oid) AS bytes FROM pg_database UNION ALL SELECT 'log', CASE WHEN current_setting('logging_collector') = 'on' THEN COALESCE((SELECT SUM(size) FROM pg_catalog.pg_ls_logdir()), 0) ELSE 0 END UNION ALL SELECT 'wal', COALESCE((SELECT SUM(size) FROM pg_catalog.pg_ls_waldir()), 0); ttl: 60 timeout: 1 min_version: 100000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Database name, or special category wal, or log" } - bytes: { usage: GAUGE ,description: "File size in bytes" } pg_exporter-1.4.1/config/0320-pg_archiver.yml000066400000000000000000000021351523237665500207350ustar00rootroot00000000000000#==============================================================# # 0320 pg_archiver #==============================================================# pg_archiver: name: pg_archiver desc: PostgreSQL archiver process statistics query: |- SELECT archived_count AS finish_count,failed_count, extract(epoch FROM last_archived_time) AS finish_time, extract(epoch FROM last_failed_time) AS failed_time, extract(epoch FROM stats_reset) AS reset_time FROM pg_stat_archiver; ttl: 60 min_version: 090400 tags: [ cluster ] metrics: - finish_count: { usage: COUNTER ,description: "Number of WAL files that have been successfully archived" } - failed_count: { usage: COUNTER ,description: "Number of failed attempts for archiving WAL files" } - finish_time: { usage: GAUGE ,description: "Time of the last successful archive operation" } - failed_time: { usage: GAUGE ,description: "Time of the last failed archival operation" } - reset_time: { usage: GAUGE ,description: "Time at which archive statistics were last reset" } pg_exporter-1.4.1/config/0330-pg_bgwriter.yml000066400000000000000000000057111523237665500207630ustar00rootroot00000000000000#==============================================================# # 0330 pg_bgwriter #==============================================================# # https://pgpedia.info/p/pg_stat_bgwriter.html pg_bgwriter_17: name: pg_bgwriter desc: "PostgreSQL background writer metrics PG 17+" query: SELECT buffers_clean, maxwritten_clean, buffers_alloc, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 170000 tags: [ cluster ] metrics: - buffers_clean: { usage: COUNTER ,description: "Number of buffers written by the background writer" } - maxwritten_clean: { usage: COUNTER ,description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers" } - buffers_alloc: { usage: COUNTER ,description: "Number of buffers allocated" } - reset_time: { usage: GAUGE ,description: "Time at which bgwriter statistics were last reset" } pg_bgwriter_10: name: pg_bgwriter desc: "PostgreSQL background writer metrics (PG 9.4-16)" query: SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint, buffers_clean, buffers_backend, maxwritten_clean, buffers_backend_fsync, buffers_alloc, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 090400 max_version: 170000 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - checkpoint_sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_checkpoint: { usage: COUNTER ,description: "Number of buffers written during checkpoints" } - buffers_clean: { usage: COUNTER ,description: "Number of buffers written by the background writer" } - buffers_backend: { usage: COUNTER ,description: "Number of buffers written directly by a backend" } - maxwritten_clean: { usage: COUNTER ,description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers" } - buffers_backend_fsync: { usage: COUNTER ,description: "Number of times a backend had to execute its own fsync call" } - buffers_alloc: { usage: COUNTER ,description: "Number of buffers allocated" } - reset_time: { usage: GAUGE ,description: "Time at which bgwriter statistics were last reset" } pg_exporter-1.4.1/config/0331-pg_checkpointer.yml000066400000000000000000000124501523237665500216130ustar00rootroot00000000000000#==============================================================# # 0331 pg_checkpointer #==============================================================# pg_checkpointer_18: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 18+" query: SELECT num_timed, num_requested, num_done, restartpoints_timed, restartpoints_req, restartpoints_done, write_time, sync_time, buffers_written, slru_written, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_checkpointer; ttl: 10 min_version: 180000 tags: [ cluster ] metrics: - num_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - num_requested: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - num_done: { usage: COUNTER ,rename: done ,description: "Number of checkpoints that have been performed" } - restartpoints_timed: { usage: COUNTER ,description: "Number of scheduled restartpoints due to timeout or after a failed attempt to perform it" } - restartpoints_req: { usage: COUNTER ,description: "Number of requested restartpoints" } - restartpoints_done: { usage: COUNTER ,description: "Number of restartpoints that have been performed" } - write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_written: { usage: COUNTER ,description: "Number of buffers written during checkpoints and restartpoints" } - slru_written: { usage: COUNTER ,description: "Number of SLRU buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } pg_checkpointer_17: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 17" query: SELECT num_timed, num_requested, restartpoints_timed, restartpoints_req, restartpoints_done, write_time, sync_time, buffers_written, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_checkpointer; ttl: 10 min_version: 170000 max_version: 180000 tags: [ cluster ] metrics: - num_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - num_requested: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - restartpoints_timed: { usage: COUNTER ,description: "Number of scheduled restartpoints due to timeout or after a failed attempt to perform it" } - restartpoints_req: { usage: COUNTER ,description: "Number of requested restartpoints" } - restartpoints_done: { usage: COUNTER ,description: "Number of restartpoints that have been performed" } - write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_written: { usage: COUNTER ,description: "Number of buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } pg_checkpointer_10: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 9.4-16" query: SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 090400 max_version: 170000 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,rename: write_time ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - checkpoint_sync_time: { usage: COUNTER ,rename: sync_time ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_checkpoint: { usage: COUNTER ,rename: buffers_written ,description: "Number of buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } pg_exporter-1.4.1/config/0340-pg_ssl.yml000066400000000000000000000011511523237665500177320ustar00rootroot00000000000000#==============================================================# # 0340 pg_ssl #==============================================================# pg_ssl: name: pg_ssl desc: PostgreSQL SSL client connection count query: | SELECT count(*) FILTER (WHERE ssl) AS enabled, count(*) FILTER ( WHERE NOT ssl) AS disabled FROM pg_stat_ssl; ttl: 10 min_version: 090500 tags: [ cluster ] metrics: - enabled: { usage: GAUGE ,description: "Number of client connection that use ssl" } - disabled: { usage: GAUGE ,description: "Number of client connection that does not use ssl" } pg_exporter-1.4.1/config/0350-pg_checkpoint.yml000066400000000000000000000056741523237665500212770ustar00rootroot00000000000000#==============================================================# # 0350 pg_checkpoint #==============================================================# pg_checkpoint: name: pg_checkpoint desc: checkpoint information from pg_control_checkpoint since 10 query: |- SELECT checkpoint_lsn - '0/0' AS checkpoint_lsn, redo_lsn - '0/0' AS redo_lsn, timeline_id AS tli, prev_timeline_id AS prev_tli, full_page_writes, split_part(next_xid, ':', 1) AS next_xid_epoch, split_part(next_xid, ':', 2) AS next_xid, next_oid::BIGINT, next_multixact_id::text::BIGINT, next_multi_offset::text::BIGINT, oldest_xid::text::BIGINT, oldest_xid_dbid::text::BIGINT, oldest_active_xid::text::BIGINT, oldest_multi_xid::text::BIGINT, oldest_multi_dbid::BIGINT, oldest_commit_ts_xid::text::BIGINT, newest_commit_ts_xid::text::BIGINT, checkpoint_time AS time, extract(epoch from now() - checkpoint_time) AS elapse FROM pg_control_checkpoint(); ttl: 60 min_version: 100000 tags: [ cluster ] metrics: - checkpoint_lsn: { usage: COUNTER ,description: "Latest checkpoint location" } - redo_lsn: { usage: COUNTER ,description: "Latest checkpoint's REDO location" } - tli: { usage: COUNTER ,description: "Latest checkpoint's TimeLineID" } - prev_tli: { usage: COUNTER ,description: "Latest checkpoint's PrevTimeLineID" } - full_page_writes: { usage: GAUGE ,description: "Latest checkpoint's full_page_writes enabled" } - next_xid_epoch: { usage: COUNTER ,description: "Latest checkpoint's NextXID epoch" } - next_xid: { usage: COUNTER ,description: "Latest checkpoint's NextXID xid" } - next_oid: { usage: COUNTER ,description: "Latest checkpoint's NextOID" } - next_multixact_id: { usage: COUNTER ,description: "Latest checkpoint's NextMultiXactId" } - next_multi_offset: { usage: COUNTER ,description: "Latest checkpoint's NextMultiOffset" } - oldest_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestXID" } - oldest_xid_dbid: { usage: GAUGE ,description: "Latest checkpoint's oldestXID's DB OID" } - oldest_active_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestActiveXID" } - oldest_multi_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestMultiXid" } - oldest_multi_dbid: { usage: GAUGE ,description: "Latest checkpoint's oldestMulti's DB OID" } - oldest_commit_ts_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestCommitTsXid" } - newest_commit_ts_xid: { usage: COUNTER ,description: "Latest checkpoint's newestCommitTsXid" } - time: { usage: COUNTER ,description: "Time of latest checkpoint" } - elapse: { usage: GAUGE ,description: "Seconds elapsed since latest checkpoint in seconds" } pg_exporter-1.4.1/config/0355-pg_timeline.yml000066400000000000000000000010061523237665500207440ustar00rootroot00000000000000#==============================================================# # 0355 pg_timeline #==============================================================# pg_timeline: name: pg_timeline desc: Current timeline ID from primary or replica query: | SELECT COALESCE( (SELECT received_tli FROM pg_stat_wal_receiver), (SELECT timeline_id FROM pg_control_checkpoint()) ) AS id; ttl: 10 min_version: 100000 tags: [ cluster ] metrics: - id: { usage: GAUGE ,description: "Current timeline ID" } pg_exporter-1.4.1/config/0360-pg_recovery.yml000066400000000000000000000051471523237665500210020ustar00rootroot00000000000000#==============================================================# # 0360 pg_recovery #==============================================================# pg_recovery: name: pg_recovery desc: PostgreSQL control recovery metrics (9.6+) query: | SELECT min_recovery_end_timeline AS min_timeline, min_recovery_end_lsn - '0/0' AS min_lsn, backup_start_lsn - '0/0' AS backup_start_lsn, backup_end_lsn - '0/0' AS backup_end_lsn, end_of_backup_record_required AS require_record FROM pg_control_recovery(); ttl: 10 min_version: 090600 tags: [ cluster, replica ] metrics: - min_timeline: { usage: COUNTER ,description: "Min recovery ending loc's timeline" } - min_lsn: { usage: COUNTER ,description: "Minimum recovery ending location" } - backup_start_lsn: { usage: COUNTER ,description: "Backup start location" } - backup_end_lsn: { usage: COUNTER ,description: "Backup end location" } - require_record: { usage: GAUGE ,description: "End-of-backup record required" } pg_recovery_prefetch: name: pg_recovery_prefetch desc: PostgreSQL recovery prefetch metrics (15+) query: SELECT prefetch,hit,skip_init,skip_new,skip_fpw,skip_rep,wal_distance,block_distance,io_depth,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_recovery_prefetch; ttl: 10 min_version: 150000 tags: [ cluster, replica ] metrics: - prefetch: { usage: COUNTER ,description: "Number of blocks prefetched because they were not in the buffer pool" } - hit: { usage: COUNTER ,description: "Number of blocks not prefetched because they were already in the buffer pool" } - skip_init: { usage: COUNTER ,description: "Number of blocks not prefetched because they would be zero-initialized" } - skip_new: { usage: COUNTER ,description: "Number of blocks not prefetched because they didn't exist yet" } - skip_fpw: { usage: COUNTER ,description: "Number of blocks not prefetched because a full page image was included in the WAL" } - skip_rep: { usage: COUNTER ,description: "Number of blocks not prefetched because they were already recently prefetched" } - wal_distance: { usage: GAUGE ,description: "How many bytes ahead the prefetcher is looking" } - block_distance: { usage: GAUGE ,description: "How many blocks ahead the prefetcher is looking" } - io_depth: { usage: GAUGE ,description: "How many prefetches have been initiated but are not yet known to have completed" } - reset_time: { usage: GAUGE ,description: "Time at which these recovery prefetch statistics were last reset" } pg_exporter-1.4.1/config/0370-pg_recovery_state.yml000066400000000000000000000040451523237665500221770ustar00rootroot00000000000000#==============================================================# # 0370 pg_recovery_state #==============================================================# pg_recovery_state: name: pg_recovery_state desc: PostgreSQL recovery state metrics from pg_stat_recovery (19+) query: |- SELECT promote_triggered, last_replayed_read_lsn - '0/0' AS last_replayed_read_lsn, last_replayed_end_lsn - '0/0' AS last_replayed_end_lsn, last_replayed_tli, replay_end_lsn - '0/0' AS replay_end_lsn, replay_end_tli, extract(EPOCH FROM recovery_last_xact_time) AS recovery_last_xact_time, extract(EPOCH FROM current_chunk_start_time) AS current_chunk_start_time, CASE pause_state WHEN 'not paused' THEN 0 WHEN 'pause requested' THEN 1 WHEN 'paused' THEN 2 ELSE NULL END AS pause_state FROM pg_stat_recovery; ttl: 10 min_version: 190000 tags: [ cluster, replica ] metrics: - promote_triggered: { usage: GAUGE ,description: "True if a promotion has been triggered" } - last_replayed_read_lsn: { usage: COUNTER ,description: "Start WAL location of the last successfully replayed WAL record" } - last_replayed_end_lsn: { usage: COUNTER ,description: "End WAL location of the last successfully replayed WAL record" } - last_replayed_tli: { usage: GAUGE ,description: "Timeline of the last successfully replayed WAL record" } - replay_end_lsn: { usage: COUNTER ,description: "End-plus-one WAL location of the record currently being replayed" } - replay_end_tli: { usage: GAUGE ,description: "Timeline of the WAL record currently being replayed" } - recovery_last_xact_time: { usage: GAUGE ,description: "Unix epoch time of the last transaction commit or abort replayed during recovery" } - current_chunk_start_time: { usage: GAUGE ,description: "Unix epoch time when startup observed replay caught up with the latest received WAL chunk" } - pause_state: { usage: GAUGE ,description: "Recovery pause state encoded as 0 not paused, 1 pause requested, 2 paused" } pg_exporter-1.4.1/config/0380-pg_slru.yml000066400000000000000000000025641523237665500201330ustar00rootroot00000000000000#==============================================================# # 0380 pg_slru #==============================================================# pg_slru_13: name: pg_slru desc: PostgreSQL simple-least-recently-used (SLRU) cache statistics v13 query: SELECT name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_slru; ttl: 60 min_version: 130000 tags: [ cluster ] metrics: - name: { usage: LABEL ,description: "Name of the SLRU" } - blks_zeroed: { usage: COUNTER ,description: "Number of blocks zeroed during initializations" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the SLRU, so that a read was not necessary" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read for this SLRU" } - blks_written: { usage: COUNTER ,description: "Number of disk blocks written for this SLRU" } - blks_exists: { usage: COUNTER ,description: "Number of blocks checked for existence for this SLRU" } - flushes: { usage: COUNTER ,description: "Number of flushes of dirty data for this SLRU" } - truncates: { usage: COUNTER ,description: "Number of truncates for this SLRU" } - reset_time: { usage: GAUGE ,description: "Time at which these statistics were last reset" } pg_exporter-1.4.1/config/0390-pg_shmem.yml000066400000000000000000000021231523237665500202470ustar00rootroot00000000000000#==============================================================# # 0390 pg_shmem #==============================================================# # pg_shmem require su privilege to work. Disable it or create auxiliary function with su before use: # CREATE OR REPLACE FUNCTION monitor.pg_shmem() RETURNS SETOF pg_shmem_allocations AS $$ SELECT * FROM pg_shmem_allocations;$$ LANGUAGE SQL SECURITY DEFINER; pg_shmem: name: pg_shmem desc: Allocations made from the server's main shared memory segment query: SELECT coalesce(name, 'Free') AS name, off AS offset, size, allocated_size FROM monitor.pg_shmem(); ttl: 60 min_version: 130000 skip: true # disable it by default tags: [cluster, "schema:monitor" ] metrics: - name: { usage: LABEL ,description: "Name of the shared memory allocation" } - offset: { usage: GAUGE ,description: "The offset at which the allocation starts" } - size: { usage: GAUGE ,description: "Size of the allocation" } - allocated_size: { usage: GAUGE ,description: "Size of the allocation including padding" } pg_exporter-1.4.1/config/0400-pg_wal.yml000066400000000000000000000071701523237665500177200ustar00rootroot00000000000000#==============================================================# # 0400 pg_wal #==============================================================# pg_wal_19: name: pg_wal desc: PostgreSQL WAL statistics since v19 query: SELECT wal_records AS records, wal_fpi AS fpi, wal_bytes AS bytes, wal_fpi_bytes AS fpi_bytes, wal_buffers_full AS buffers_full, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_wal; ttl: 10 tags: [ cluster ] min_version: 190000 metrics: - records: { usage: COUNTER ,description: "Total number of WAL records generated" } - fpi: { usage: COUNTER ,description: "Total number of WAL full page images generated" } - bytes: { usage: COUNTER ,description: "Total amount of WAL generated in bytes" } - fpi_bytes: { usage: COUNTER ,description: "Total amount of WAL full page images generated in bytes" } - buffers_full: { usage: COUNTER ,description: "Number of times WAL data was written to disk because WAL buffers became full" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } pg_wal_18: name: pg_wal desc: PostgreSQL WAL statistics since v18 with some col removed query: SELECT wal_records AS records, wal_fpi AS fpi, wal_bytes AS bytes, wal_buffers_full AS buffers_full,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_wal; ttl: 10 tags: [ cluster ] min_version: 180000 max_version: 190000 metrics: - records: { usage: COUNTER ,description: "Total number of WAL records generated" } - fpi: { usage: COUNTER ,description: "Total number of WAL full page images generated" } - bytes: { usage: COUNTER ,description: "Total amount of WAL generated in bytes" } - buffers_full: { usage: COUNTER ,description: "Number of times WAL data was written to disk because WAL buffers became full" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } pg_wal_14: name: pg_wal desc: PostgreSQL WAL statistics since v14 query: SELECT wal_records AS records, wal_fpi AS fpi, wal_bytes AS bytes, wal_buffers_full AS buffers_full, wal_write AS write, wal_sync AS sync, wal_write_time AS write_time, wal_sync_time AS sync_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_wal; ttl: 10 tags: [ cluster ] min_version: 140000 max_version: 180000 metrics: - records: { usage: COUNTER ,description: "Total number of WAL records generated" } - fpi: { usage: COUNTER ,description: "Total number of WAL full page images generated" } - bytes: { usage: COUNTER ,description: "Total amount of WAL generated in bytes" } - buffers_full: { usage: COUNTER ,description: "Number of times WAL data was written to disk because WAL buffers became full" } - write: { usage: COUNTER ,description: "Number of times WAL buffers were written out to disk via XLogWrite request." } - sync: { usage: COUNTER ,description: "Number of times WAL files were synced to disk via issue_xlog_fsync request" } - write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time spent writing WAL buffers to disk via XLogWrite request in seconds" } - sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time spent syncing WAL files to disk via issue_xlog_fsync request, in seconds" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } pg_exporter-1.4.1/config/0410-pg_activity.yml000066400000000000000000000034141523237665500207670ustar00rootroot00000000000000#==============================================================# # 0410 pg_activity #==============================================================# pg_activity: name: pg_activity desc: PostgreSQL backend activity group by database and state query: |- SELECT datname, state, coalesce(count, 0) AS count, coalesce(max_duration, 0) AS max_duration, coalesce(max_tx_duration, 0) AS max_tx_duration, coalesce(max_conn_duration, 0) AS max_conn_duration FROM (SELECT d.datname, a.state FROM pg_database d, unnest(ARRAY ['active','idle','idle in transaction','idle in transaction (aborted)','fastpath function call','disabled']) a(state) WHERE d.datallowconn AND NOT d.datistemplate) base LEFT JOIN (SELECT datname, state, count(*) AS count, max(extract(epoch from now() - state_change)) AS max_duration, max(extract(epoch from now() - xact_start)) AS max_tx_duration, max(extract(epoch from now() - backend_start)) AS max_conn_duration FROM pg_stat_activity WHERE pid <> pg_backend_pid() GROUP BY 1,2) data USING (datname,state); ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - state: { usage: LABEL ,description: "Current overall state of this backend." } - count: { usage: GAUGE ,description: "Count of connection among (datname,state)" } - max_duration: { usage: GAUGE ,description: "Max duration since last state change among (datname, state)" } - max_tx_duration: { usage: GAUGE ,description: "Max transaction duration since state change among (datname, state)" } - max_conn_duration: { usage: GAUGE ,description: "Max backend session duration since state change among (datname, state)" } pg_exporter-1.4.1/config/0420-pg_wait.yml000066400000000000000000000013221523237665500200740ustar00rootroot00000000000000#==============================================================# # 0420 pg_wait #==============================================================# pg_wait: name: pg_wait desc: PostgreSQL backend client count group by wait event type since 9.6 query: | SELECT coalesce(datname, '_system') AS datname, coalesce(wait_event_type, 'Running') AS event, count(*) AS count FROM pg_stat_activity GROUP BY 1, 2; ttl: 10 min_version: 090600 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database, _system for global process" } - event: { usage: LABEL ,description: "Wait event type" } - count: { usage: GAUGE ,description: "Count of WaitEvent on target database" } pg_exporter-1.4.1/config/0430-pg_backend.yml000066400000000000000000000011161523237665500205210ustar00rootroot00000000000000#==============================================================# # 0430 pg_backend #==============================================================# pg_backend: name: pg_backend desc: PostgreSQL backend client count group by wait event type since 10 query: SELECT backend_type AS "type", count(*) AS count FROM pg_stat_activity GROUP BY backend_type; ttl: 10 min_version: 100000 tags: [ cluster ] metrics: - type: { usage: LABEL ,description: "Database backend process type" } - count: { usage: GAUGE ,description: "Database backend process count by backend_type" } pg_exporter-1.4.1/config/0440-pg_xact.yml000066400000000000000000000015661523237665500201030ustar00rootroot00000000000000#==============================================================# # 0440 pg_xact #==============================================================# pg_xact: name: pg_xact desc: PostgreSQL transaction identifier metrics query: WITH snap(v) AS (SELECT txid_current_snapshot()), xset(v) AS (SELECT txid_snapshot_xip(v) FROM snap), xnum(v) AS (SELECT count(*) from xset), xmin(v) AS (SELECT txid_snapshot_xmin(v) FROM snap), xmax(v) AS (SELECT txid_snapshot_xmax(v) FROM snap) SELECT xmin.v AS xmin, xmax.v AS xmax, xnum.v AS xnum FROM xmin, xmax, xnum; ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - xmin: { usage: COUNTER ,description: "Earliest txid that is still active" } - xmax: { usage: COUNTER ,description: "First as-yet-unassigned txid. txid >= this are invisible." } - xnum: { usage: GAUGE ,description: "Current active transaction count" } pg_exporter-1.4.1/config/0450-pg_xact_age.yml000066400000000000000000000030211523237665500207040ustar00rootroot00000000000000#==============================================================# # 0450 pg_xact_age #==============================================================# pg_xact_age: name: pg_xact_age desc: PostgreSQL open transaction age snapshot histograms by database query: |- SELECT datname, greatest(0, extract(epoch FROM now() - xact_start)) AS seconds, CASE WHEN state LIKE 'idle in transaction%' THEN greatest(0, extract(epoch FROM now() - xact_start)) END AS idle_seconds FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND backend_type = 'client backend' AND datname IS NOT NULL AND xact_start IS NOT NULL; ttl: 10 min_version: 100000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } # 1-3-10 log grid from 1s to ~28h; sub-second ages are sampling noise for snapshots. # seconds covers every open transaction, including implicit autocommit ones in # flight, so _count is the current open-transaction count; low buckets absorb # that healthy churn. - seconds: { usage: HISTOGRAM ,bucket: [1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000] ,description: "Open transaction age snapshot in seconds" } # idle_seconds covers the idle-in-transaction subset; capped at 3000s (~50min) # since anything older is uniformly critical and lands in +Inf. - idle_seconds: { usage: HISTOGRAM ,bucket: [1, 3, 10, 30, 100, 300, 1000, 3000] ,description: "Idle-in-transaction age snapshot in seconds" } pg_exporter-1.4.1/config/0460-pg_lock.yml000066400000000000000000000021201523237665500200610ustar00rootroot00000000000000#==============================================================# # 0460 pg_lock #==============================================================# pg_lock: name: pg_lock desc: PostgreSQL lock distribution by mode and database query: | SELECT datname, mode, coalesce(count, 0) AS count FROM (SELECT d.oid AS database, d.datname, l.mode FROM pg_database d, unnest(ARRAY ['AccessShareLock','RowShareLock','RowExclusiveLock','ShareUpdateExclusiveLock', 'ShareLock','ShareRowExclusiveLock','ExclusiveLock','AccessExclusiveLock']) l(mode) WHERE d.datallowconn AND NOT d.datistemplate) base LEFT JOIN (SELECT database, mode, count(*) AS count FROM pg_locks WHERE database IS NOT NULL GROUP BY 1, 2) cnt USING (database, mode); ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - mode: { usage: LABEL ,description: "Name of the lock mode held or desired by this process" } - count: { usage: GAUGE ,description: "Number of locks of corresponding mode and database" } pg_exporter-1.4.1/config/0470-pg_lock_stat.yml000066400000000000000000000020351523237665500211220ustar00rootroot00000000000000#==============================================================# # 0470 pg_lock_stat #==============================================================# pg_lock_stat: name: pg_lock_stat desc: PostgreSQL cluster-wide lock statistics by lock type (19+) query: |- SELECT locktype, waits, wait_time, fastpath_exceeded FROM pg_stat_lock; ttl: 10 min_version: 190000 tags: [ cluster ] metrics: - locktype: { usage: LABEL ,description: "Type of the lockable object" } - waits: { usage: COUNTER ,description: "Number of times a lock of this type was acquired after waiting longer than deadlock_timeout because of a conflicting lock" } - wait_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent waiting for locks of this type in seconds, only counting waits longer than deadlock_timeout (scaled from PG milliseconds)" } - fastpath_exceeded: { usage: COUNTER ,description: "Number of times a lock of this type could not be acquired via fast path because the slot limit was exceeded" } pg_exporter-1.4.1/config/0480-pg_query.yml000066400000000000000000000135071523237665500203130ustar00rootroot00000000000000#==============================================================# # 0480 pg_query #==============================================================# pg_query_17: name: pg_query desc: PostgreSQL Query metrics, require pg_stat_statements installed, 17+ query: |- SELECT datname, queryid AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_exec_time) AS exec_time, sum(shared_blk_read_time) + sum(shared_blk_write_time) AS io_time, sum(wal_bytes) AS wal_bytes ,sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, sum(shared_blks_dirtied) AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements(false) s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 170000 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "QueryID generated from internal hash code, computed from the statement's parse tree" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds" } - wal_bytes: { usage: COUNTER ,description: "Total amount of WAL bytes generated by the statement" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } pg_query_13: name: pg_query desc: PostgreSQL Query metrics, require pg_stat_statements installed, 13 - 16 query: |- SELECT datname, queryid AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_exec_time) AS exec_time, sum(blk_read_time) + sum(blk_write_time) AS io_time, sum(wal_bytes) AS wal_bytes ,sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, sum(shared_blks_dirtied) AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements(false) s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 130000 max_version: 170000 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "QueryID generated from internal hash code, computed from the statement's parse tree" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds" } - wal_bytes: { usage: COUNTER ,description: "Total amount of WAL bytes generated by the statement" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } pg_query_10: name: pg_query desc: PostgreSQL query statement metrics, require pg_stat_statements installed, 9.4 ~ 12 query: |- SELECT datname, queryid AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_time) AS exec_time, sum(blk_read_time) + sum(blk_write_time) AS io_time, sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, sum(shared_blks_dirtied) AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements(false) s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 090400 max_version: 130000 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "QueryID generated from internal hash code, computed from the statement's parse tree" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } pg_exporter-1.4.1/config/0510-pg_vacuuming.yml000066400000000000000000000075331523237665500211400ustar00rootroot00000000000000#==============================================================# # 0510 pg_vacuuming #==============================================================# pg_vacuuming_18: name: pg_vacuuming desc: PostgreSQL vacuum progress 18+ query: |- SELECT datname, pid, relid::RegClass AS relname, CASE phase WHEN 'scanning heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_scanned / heap_blks_total ELSE 0.0 END) WHEN 'vacuuming heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_vacuumed / heap_blks_total ELSE 0 END) ELSE NULL END AS progress, indexes_total, indexes_processed, dead_tuple_bytes, delay_time FROM pg_stat_progress_vacuum; ttl: 10 min_version: 180000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "database name" } - pid: { usage: LABEL ,description: "process id of vacuum worker" } - relname: { usage: LABEL ,description: "relation name of vacuuming table" } - progress: { usage: GAUGE ,description: "vacuum progress ratio (0-1) based on heap blocks scanned/vacuumed" } - indexes_total: { usage: GAUGE ,description: "total number of indexes that will be vacuumed or cleaned up" } - indexes_processed: { usage: GAUGE ,description: "number of indexes that have been vacuumed or cleaned up" } - dead_tuple_bytes: { usage: GAUGE ,description: "total size of dead tuples collected since the beginning of vacuum in bytes" } - delay_time: { usage: COUNTER ,scale: 1e-3 ,description: "total time spent sleeping due to cost-based delay in seconds" } pg_vacuuming_17: name: pg_vacuuming desc: PostgreSQL vacuum progress 17 (with index progress tracking) query: |- SELECT datname, pid, relid::RegClass AS relname, CASE phase WHEN 'scanning heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_scanned / heap_blks_total ELSE 0.0 END) WHEN 'vacuuming heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_vacuumed / heap_blks_total ELSE 0 END) ELSE NULL END AS progress, indexes_total, indexes_processed, dead_tuple_bytes FROM pg_stat_progress_vacuum; ttl: 10 min_version: 170000 max_version: 180000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "database name" } - pid: { usage: LABEL ,description: "process id of vacuum worker" } - relname: { usage: LABEL ,description: "relation name of vacuuming table" } - progress: { usage: GAUGE ,description: "vacuum progress ratio (0-1) based on heap blocks scanned/vacuumed" } - indexes_total: { usage: GAUGE ,description: "total number of indexes that will be vacuumed or cleaned up" } - indexes_processed: { usage: GAUGE ,description: "number of indexes that have been vacuumed or cleaned up" } - dead_tuple_bytes: { usage: GAUGE ,description: "total size of dead tuples collected since the beginning of vacuum in bytes" } pg_vacuuming_12: name: pg_vacuuming desc: PostgreSQL vacuum progress 12-16 query: |- SELECT datname, pid, relid::RegClass AS relname, CASE phase WHEN 'scanning heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_scanned / heap_blks_total ELSE 0.0 END) WHEN 'vacuuming heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_vacuumed / heap_blks_total ELSE 0 END) ELSE NULL END AS progress FROM pg_stat_progress_vacuum; ttl: 10 min_version: 120000 max_version: 170000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "database name" } - pid: { usage: LABEL ,description: "process id of vacuum worker" } - relname: { usage: LABEL ,description: "relation name of vacuuming table" } - progress: { usage: GAUGE ,description: "vacuum progress ratio (0-1) based on heap blocks scanned/vacuumed" } pg_exporter-1.4.1/config/0520-pg_indexing.yml000066400000000000000000000026031523237665500207410ustar00rootroot00000000000000#==============================================================# # 0520 pg_indexing #==============================================================# pg_indexing: name: pg_indexing desc: PostgreSQL index creating progress (v12+) query: |- SELECT datname, pid, relid::RegClass AS relname, (CASE WHEN blocks_total > 0 THEN 1.0 * blocks_done / blocks_total ELSE NULL END) AS blocks, (CASE WHEN tuples_total > 0 THEN 1.0 * tuples_done / tuples_total ELSE NULL END) AS tuples, (CASE WHEN partitions_total > 0 THEN 1.0 * partitions_done / partitions_total ELSE NULL END) AS partitions, (CASE WHEN lockers_total > 0 THEN 1.0 * lockers_done / lockers_total ELSE NULL END) AS lockers FROM pg_stat_progress_create_index pspci; ttl: 10 min_version: 120000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - pid: { usage: LABEL ,description: "Process id of indexing table" } - relname: { usage: LABEL ,description: "Relation name of indexed table" } - blocks: { usage: GAUGE ,description: "Percent of blocks been proceeded" } - tuples: { usage: GAUGE ,description: "Percent of tuples been proceeded" } - partitions: { usage: GAUGE ,description: "Percent of partitions been proceeded" } - lockers: { usage: GAUGE ,description: "Percent of lockers been proceeded" } pg_exporter-1.4.1/config/0530-pg_clustering.yml000066400000000000000000000016731523237665500213220ustar00rootroot00000000000000#==============================================================# # 0530 pg_clustering #==============================================================# pg_clustering: name: pg_clustering desc: PostgreSQL cluster or vacuum full progress (v12+) query: SELECT datname, pid, relid::RegClass AS relname, heap_tuples_scanned AS tup_scan, CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_scanned / heap_blks_total ELSE 0 END AS progress FROM pg_stat_progress_cluster; ttl: 10 min_version: 120000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "Name of database been clustering" } - pid: { usage: LABEL ,description: "Process id of indexing table" } - relname: { usage: LABEL ,description: "Relation name of indexed table" } - tup_scan: { usage: GAUGE ,description: "How much tuple been scanned" } - progress: { usage: GAUGE ,description: "Progress of heap been processed" } pg_exporter-1.4.1/config/0540-pg_backup.yml000066400000000000000000000015721523237665500204070ustar00rootroot00000000000000#==============================================================# # 0540 pg_backup #==============================================================# pg_backup: name: pg_backup desc: PostgreSQL basebackup progress since 13 query: SELECT pid, param1 AS phase, CASE param2 WHEN -1::integer THEN NULL::bigint ELSE param2 END AS total_bytes, param3 AS sent_bytes FROM pg_stat_get_progress_info('BASEBACKUP'); ttl: 10 min_version: 130000 tags: [ cluster ] metrics: - pid: { usage: LABEL ,description: "process id of basebackup sender" } - phase: { usage: GAUGE ,description: "Phase encoded in 0~5 initial, wait checkpoint, estimate, streaming, waiting archive, transfer archive" } - total_bytes: { usage: GAUGE ,description: "Total amount of data that will be streamed" } - sent_bytes: { usage: GAUGE ,description: "Amount of data streamed" } pg_exporter-1.4.1/config/0610-pg_db.yml000066400000000000000000000473111523237665500175260ustar00rootroot00000000000000#==============================================================# # 0610 pg_db #==============================================================# pg_db_18: name: pg_db desc: PostgreSQL database stats from pg_stat_database v18 query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total,blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,coalesce(checksum_failures, -1) AS cks_fails, checksum_last_failure AS cks_fail_time,blk_read_time,blk_write_time, session_time,active_time,idle_in_transaction_time AS ixact_time,sessions,sessions_abandoned,sessions_fatal,sessions_killed,parallel_workers_to_launch,parallel_workers_launched, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 180000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - cks_fails: { usage: COUNTER ,description: "Number of data page checksum failures detected in this database, -1 for not enabled" } - cks_fail_time: { usage: GAUGE ,description: "Time at which the last data page checksum failure was detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - session_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent by database sessions in this database, in seconds" } - active_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent executing SQL statements in this database, in seconds" } - ixact_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent idling while in a transaction in this database, in seconds" } - sessions: { usage: COUNTER ,description: "Total number of sessions established to this database" } - sessions_abandoned: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated because connection to the client was lost" } - sessions_fatal: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated by fatal errors" } - sessions_killed: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated by operator intervention" } - parallel_workers_to_launch: { usage: COUNTER ,description: "Number of parallel workers planned to be launched by queries on this database" } - parallel_workers_launched: { usage: COUNTER ,description: "Number of parallel workers launched by queries on this database" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_db_14: name: pg_db desc: PostgreSQL database stats from pg_stat_database v14 (with 7 new time & session metrics) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total,blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,coalesce(checksum_failures, -1) AS cks_fails, checksum_last_failure AS cks_fail_time,blk_read_time,blk_write_time, session_time,active_time,idle_in_transaction_time AS ixact_time,sessions,sessions_abandoned,sessions_fatal,sessions_killed,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 140000 max_version: 180000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - cks_fails: { usage: COUNTER ,description: "Number of data page checksum failures detected in this database, -1 for not enabled" } - cks_fail_time: { usage: GAUGE ,description: "Time at which the last data page checksum failure was detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - session_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent by database sessions in this database, in seconds" } - active_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent executing SQL statements in this database, in seconds" } - ixact_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent idling while in a transaction in this database, in seconds" } - sessions: { usage: COUNTER ,description: "Total number of sessions established to this database" } - sessions_abandoned: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated because connection to the client was lost" } - sessions_fatal: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated by fatal errors" } - sessions_killed: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated by operator intervention" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_db_12: name: pg_db desc: PostgreSQL database stats from pg_stat_database v12 v13 (with 2 new checksum metrics) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total,blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,coalesce(checksum_failures, -1) AS cks_fails, checksum_last_failure AS cks_fail_time,blk_read_time,blk_write_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 120000 max_version: 140000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - cks_fails: { usage: COUNTER ,description: "Number of data page checksum failures detected in this database, -1 for not enabled" } - cks_fail_time: { usage: GAUGE ,description: "Time at which the last data page checksum failure was detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_db_10: name: pg_db desc: PostgreSQL database stats from pg_stat_database v10 v11 (actually since 9.2) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total, blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,blk_read_time,blk_write_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 090200 max_version: 120000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_exporter-1.4.1/config/0620-pg_db_confl.yml000066400000000000000000000053301523237665500207030ustar00rootroot00000000000000#==============================================================# # 0620 pg_db_confl #==============================================================# # https://pgpedia.info/p/pg_stat_database_conflicts.html pg_db_confl_16: name: pg_db_confl desc: PostgreSQL database conflicts metrics for PG16+ query: SELECT datid, datname, confl_tablespace, confl_lock, confl_snapshot, confl_bufferpin, confl_deadlock, confl_active_logicalslot FROM pg_stat_database_conflicts; ttl: 10 min_version: 160000 tags: [ cluster, replica ] metrics: - datid: { usage: DISCARD } - datname: { usage: LABEL ,description: "Name of this database" } - confl_tablespace: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to dropped tablespaces" } - confl_lock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to lock timeouts" } - confl_snapshot: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to old snapshots" } - confl_bufferpin: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to pinned buffers" } - confl_deadlock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to deadlocks" } - confl_active_logicalslot: { usage: COUNTER ,description: "Number of uses of logical slots in this database that have been canceled due to old snapshots or too low a wal_level on the primary" } pg_db_confl_15: name: pg_db_confl desc: PostgreSQL database conflicts metrics for pg 9.1 - 15 query: SELECT datid, datname, confl_tablespace, confl_lock, confl_snapshot, confl_bufferpin, confl_deadlock FROM pg_stat_database_conflicts; ttl: 10 min_version: 90100 max_version: 160000 tags: [ cluster, replica ] metrics: - datid: { usage: DISCARD } - datname: { usage: LABEL ,description: "Name of this database" } - confl_tablespace: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to dropped tablespaces" } - confl_lock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to lock timeouts" } - confl_snapshot: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to old snapshots" } - confl_bufferpin: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to pinned buffers" } - confl_deadlock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to deadlocks" } pg_exporter-1.4.1/config/0640-pg_pubrel.yml000066400000000000000000000013211523237665500204240ustar00rootroot00000000000000#==============================================================# # 0640 pg_pubrel #==============================================================# pg_pubrel: name: pg_pubrel desc: PostgreSQL publication and relation count query: SELECT CURRENT_CATALOG AS datname, pubname, count(*) AS count FROM pg_publication p, LATERAL pg_get_publication_tables(pubname) GROUP BY pubname; ttl: 10 min_version: 100000 metrics: - datname: { usage: LABEL ,description: "Name of the database which publication belonged" } - pubname: { usage: LABEL ,description: "Name of the publication" } - count: { usage: GAUGE ,description: "Count of relation in the publication" } pg_exporter-1.4.1/config/0650-pg_subrel.yml000066400000000000000000000017771523237665500204470ustar00rootroot00000000000000#==============================================================# # 0650 pg_subrel #==============================================================# pg_subrel: name: pg_subrel desc: PostgreSQL subscripted relation group by state query: SELECT CURRENT_CATALOG AS datname, ss.subname, sr.srsubstate::TEXT AS state, count(*) AS count FROM pg_catalog.pg_subscription_rel AS sr JOIN (SELECT DISTINCT subid, subname FROM pg_catalog.pg_stat_subscription) AS ss ON ss.subid = sr.srsubid GROUP BY ss.subname, sr.srsubstate; ttl: 10 min_version: 100000 metrics: - datname: { usage: LABEL ,description: "Name of the database which publication belonged" } - subname: { usage: LABEL ,description: "Name of the subscription" } - state: { usage: LABEL ,description: "State of table in subscription, i=initialize, d=data copy, s=sync, r=ready" } - count: { usage: GAUGE ,description: "Count of relation in this subscription and corresponding state" } pg_exporter-1.4.1/config/0660-pg_vacuum_score.yml000066400000000000000000000053321523237665500216360ustar00rootroot00000000000000#==============================================================# # 0660 pg_vacuum_score #==============================================================# pg_vacuum_score: name: pg_vacuum_score desc: PostgreSQL current database autovacuum score summary from pg_stat_autovacuum_scores (19+) query: |- SELECT CURRENT_CATALOG AS datname, COALESCE(max(score), 0) AS max_score, COALESCE(max(xid_score), 0) AS max_xid_score, COALESCE(max(mxid_score), 0) AS max_mxid_score, COALESCE(max(vacuum_score), 0) AS max_vacuum_score, COALESCE(max(vacuum_insert_score), 0) AS max_vacuum_insert_score, COALESCE(max(analyze_score), 0) AS max_analyze_score, count(*) AS table_count, count(*) FILTER (WHERE do_vacuum OR do_analyze) AS candidate_count, count(*) FILTER (WHERE do_vacuum) AS vacuum_candidate_count, count(*) FILTER (WHERE do_analyze) AS analyze_candidate_count, count(*) FILTER (WHERE for_wraparound) AS wraparound_candidate_count FROM pg_stat_autovacuum_scores; ttl: 10 timeout: 1 min_version: 190000 tags: [ primary ] metrics: - datname: { usage: LABEL ,description: "Name of the current database" } - max_score: { usage: GAUGE ,description: "Maximum autovacuum priority score across tables in the current database" } - max_xid_score: { usage: GAUGE ,description: "Maximum transaction ID age component score across tables in the current database" } - max_mxid_score: { usage: GAUGE ,description: "Maximum multixact ID age component score across tables in the current database" } - max_vacuum_score: { usage: GAUGE ,description: "Maximum vacuum component score across tables in the current database" } - max_vacuum_insert_score: { usage: GAUGE ,description: "Maximum vacuum insert component score across tables in the current database" } - max_analyze_score: { usage: GAUGE ,description: "Maximum analyze component score across tables in the current database" } - table_count: { usage: GAUGE ,description: "Number of tables represented in pg_stat_autovacuum_scores for the current database, including TOAST tables" } - candidate_count: { usage: GAUGE ,description: "Number of tables that autovacuum would vacuum or analyze at query time" } - vacuum_candidate_count: { usage: GAUGE ,description: "Number of tables that autovacuum would vacuum at query time" } - analyze_candidate_count: { usage: GAUGE ,description: "Number of tables that autovacuum would analyze at query time" } - wraparound_candidate_count: { usage: GAUGE ,description: "Number of tables that autovacuum would vacuum for wraparound prevention at query time" } pg_exporter-1.4.1/config/0700-pg_table.yml000066400000000000000000000674221523237665500202350ustar00rootroot00000000000000#==============================================================# # 0700 pg_table #==============================================================# pg_table_18: name: pg_table desc: PostgreSQL table metrics v18+ query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_tup_newpage_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.n_ins_since_vacuum,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze,psut.last_seq_scan, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psut.total_vacuum_time AS vacuum_time,psut.total_autovacuum_time AS autovacuum_time,psut.total_analyze_time AS analyze_time,psut.total_autoanalyze_time AS autoanalyze_time, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 180000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116,p/partitioned/112" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_tup_newpage_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated where the successor version goes onto a new heap page" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - n_ins_since_vacuum: { usage: GAUGE ,description: "Estimated number of rows inserted since this table was last vacuumed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - last_seq_scan: { usage: DISCARD ,description: "The timestamp of the last seq scan on this table" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - vacuum_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Total time this table has been manually vacuumed, in seconds" } - autovacuum_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Total time this table has been vacuumed by the autovacuum daemon, in seconds" } - analyze_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Total time this table has been manually analyzed, in seconds" } - autoanalyze_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Total time this table has been analyzed by the autovacuum daemon, in seconds" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_table_16: name: pg_table desc: PostgreSQL table metrics 16-17 query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_tup_newpage_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.n_ins_since_vacuum,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze,psut.last_seq_scan, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 160000 max_version: 180000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116,p/partitioned/112" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_tup_newpage_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated where the successor version goes onto a new heap page" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - n_ins_since_vacuum: { usage: GAUGE ,description: "Estimated number of rows inserted since this table was last vacuumed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - last_seq_scan: { usage: DISCARD ,description: "The timestamp of the last seq scan on this table" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_table_13: name: pg_table desc: PostgreSQL table metrics 13-15 (with n_ins_since_vacuum) query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.n_ins_since_vacuum,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 130000 max_version: 160000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116,p/partitioned/112" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - n_ins_since_vacuum: { usage: GAUGE ,description: "Estimated number of rows inserted since this table was last vacuumed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_table_10: name: pg_table desc: PostgreSQL table metrics 9.4-12 query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 090400 max_version: 130000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116,p/partitioned/112" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_exporter-1.4.1/config/0710-pg_index.yml000066400000000000000000000043201523237665500202420ustar00rootroot00000000000000#==============================================================# # 0710 pg_index #==============================================================# pg_index: name: pg_index desc: PostgreSQL index metrics query: |- SELECT CURRENT_CATALOG AS datname, schemaname || '.' || indexrelname AS idxname, schemaname || '.' || relname AS relname ,indexrelid AS relid, relpages, reltuples, idx_scan, idx_tup_read, idx_tup_fetch, idx_blks_read, idx_blks_hit FROM pg_stat_user_indexes psui, LATERAL (SELECT idx_blks_read, idx_blks_hit FROM pg_statio_user_indexes psio WHERE psio.indexrelid = psui.indexrelid LIMIT 1) p2, LATERAL (SELECT relpages,reltuples FROM pg_class c WHERE c.oid = psui.indexrelid LIMIT 1) p3 WHERE schemaname !~ '^pg_' AND schemaname !~ '^_' AND schemaname !~ '^timescaledb' AND schemaname !~ '^citus' AND schemaname !~ '^columnar' AND schemaname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') ORDER BY idx_tup_read DESC LIMIT 512; ttl: 10 timeout: 1 min_version: 090400 metrics: - datname: { usage: LABEL ,description: "Database name of this index" } - idxname: { usage: LABEL ,description: "Name of this index (full-qualified schema name)" } - relname: { usage: LABEL ,description: "Name of the table for this index (full-qualified schema name)" } - relid: { usage: LABEL ,description: "Relation oid of this index" } - relpages: { usage: GAUGE ,description: "Size of the on-disk representation of this index in pages" } - reltuples: { usage: GAUGE ,description: "Estimate relation tuples" } - idx_scan: { usage: COUNTER ,description: "Number of index scans initiated on this index" } - idx_tup_read: { usage: COUNTER ,description: "Number of index entries returned by scans on this index" } - idx_tup_fetch: { usage: COUNTER ,description: "Number of live table rows fetched by simple index scans using this index" } - idx_blks_read: { usage: COUNTER ,description: "Number of disk blocks read from this index" } - idx_blks_hit: { usage: COUNTER ,description: "Number of buffer hits in this index" } pg_exporter-1.4.1/config/0720-pg_func.yml000066400000000000000000000021271523237665500200720ustar00rootroot00000000000000#==============================================================# # 0720 pg_func #==============================================================# pg_func: desc: PostgreSQL function metrics query: SELECT CURRENT_CATALOG AS datname, schemaname || '.' || funcname AS funcname, sum(calls) AS calls, sum(total_time) AS total_time, sum(self_time) AS self_time FROM pg_stat_user_functions GROUP BY 2 ORDER BY 4 DESC LIMIT 128; ttl: 10 min_version: 090400 metrics: - datname: { usage: LABEL ,description: "Name of belonged database" } - funcname: { usage: LABEL ,description: "Name of this function, may have multiple override" } - calls: { usage: COUNTER ,description: "Number of times this function has been called" } - total_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent in this function and all other functions called by it, in seconds" } - self_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent in this function itself, not including other functions called by it, in seconds" } pg_exporter-1.4.1/config/0730-pg_seq.yml000066400000000000000000000020331523237665500177240ustar00rootroot00000000000000#==============================================================# # 0730 pg_seq #==============================================================# pg_seq: desc: PostgreSQL sequence metrics query: SELECT CURRENT_CATALOG AS datname, schemaname || '.' || sequencename AS seqname, last_value, blks_read, blks_hit FROM pg_sequences s, LATERAL (SELECT relid, blks_read, blks_hit FROM pg_statio_all_sequences sio WHERE s.schemaname = sio.schemaname AND s.sequencename = sio.relname LIMIT 1) d LIMIT 128; ttl: 10 min_version: 100000 metrics: - datname: { usage: LABEL ,description: "Database name of this sequence" } - seqname: { usage: LABEL ,description: "Fully schema qualified sequence name" } - last_value: { usage: COUNTER ,description: "The last sequence value written to disk" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read from this sequence" } - blks_hit: { usage: COUNTER ,description: "Number of buffer hits in this sequence" } pg_exporter-1.4.1/config/0740-pg_relkind.yml000066400000000000000000000013031523237665500205640ustar00rootroot00000000000000#==============================================================# # 0740 pg_relkind #==============================================================# pg_relkind: name: pg_relkind desc: Postgres relation count by kind (category, r,i,m,t,...) query: SELECT CURRENT_CATALOG AS datname, relkind, count(*) AS count FROM pg_class GROUP BY relkind; ttl: 60 timeout: 1 min_version: 090400 metrics: - datname: { usage: LABEL ,description: "Name of database" } - relkind: { usage: LABEL ,description: "Kind of this relation, could be r,i,S,t,v,m,c,f,p,I" } - count: { usage: GAUGE ,description: "Number of relations of corresponding relkind" } pg_exporter-1.4.1/config/0750-pg_defpart.yml000066400000000000000000000015341523237665500205700ustar00rootroot00000000000000#==============================================================# # 0750 pg_defpart #==============================================================# pg_defpart: name: pg_defpart desc: PostgreSQL default partition tuples query: SELECT CURRENT_CATALOG AS datname, relnamespace::RegNamespace || '.' || relname AS relname, reltuples AS tuples FROM pg_class WHERE relpartbound IS NOT NULL AND pg_catalog.pg_get_expr(relpartbound, oid) = 'DEFAULT' ORDER BY reltuples DESC LIMIT 64; ttl: 60 timeout: 1 min_version: 110000 metrics: - datname: { usage: LABEL ,description: "Database name of this default partition" } - relname: { usage: LABEL ,description: "Schema qualified default partition relation name" } - tuples: { usage: GAUGE ,description: "Number of tuples in this default partition" } pg_exporter-1.4.1/config/0810-pg_table_size.yml000066400000000000000000000027101523237665500212560ustar00rootroot00000000000000#==============================================================# # 0810 pg_table_size #==============================================================# pg_table_size: desc: PostgreSQL table size metrics, quite slow query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || rel.relname AS relname, pg_total_relation_size(rel.oid) AS bytes, pg_relation_size(rel.oid) AS relsize, pg_indexes_size(rel.oid) AS indexsize, pg_total_relation_size(reltoastrelid) AS toastsize FROM pg_namespace nsp JOIN pg_class rel ON nsp.oid = rel.relnamespace WHERE nspname <> ALL(ARRAY['pg_catalog', 'information_schema']) AND rel.relkind = 'r' ORDER BY 3 DESC NULLS LAST LIMIT 256; ttl: 300 timeout: 2 min_version: 100000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Schema qualified table name" } - bytes: { usage: GAUGE ,default: 0 ,description: "Total bytes of this table (including toast, index, toast index)" } - relsize: { usage: GAUGE ,default: 0 ,description: "Bytes of this table itself (main, vm, fsm)" } - indexsize: { usage: GAUGE ,default: 0 ,description: "Bytes of all related indexes of this table" } - toastsize: { usage: GAUGE ,default: 0 ,description: "Bytes of toast tables of this table" } pg_exporter-1.4.1/config/0820-pg_table_bloat.yml000066400000000000000000000017001523237665500214040ustar00rootroot00000000000000#==============================================================# # 0820 pg_table_bloat #==============================================================# # pg_table_bloat require auxiliary view to work. Disable it or create auxiliary view before use: pg_table_bloat: name: pg_table_bloat desc: PostgreSQL table bloat metrics, require auxiliary view pg_table_bloat to work query: SELECT datname, nspname || '.' || relname AS relname, size, ratio FROM pg_table_bloat ORDER BY size DESC LIMIT 64; ttl: 300 timeout: 2 min_version: 090400 skip: true metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Schema qualified name of this table" } - size: { usage: GAUGE ,description: "Total bytes of this table" } - ratio: { usage: GAUGE ,description: "Estimated bloat ratio of this table from 0 to 1" } pg_exporter-1.4.1/config/0830-pg_index_bloat.yml000066400000000000000000000016471523237665500214370ustar00rootroot00000000000000#==============================================================# # 0830 pg_index_bloat #==============================================================# # pg_index_bloat require auxiliary view to work. Disable it or create auxiliary view before use: pg_index_bloat: name: pg_index_bloat desc: PostgreSQL index bloat metrics (btree only), require pg_index_bloat query: SELECT datname, nspname || '.' || relname AS relname, size, ratio FROM pg_index_bloat ORDER BY size DESC LIMIT 64; ttl: 300 timeout: 2 min_version: 090400 skip: true metrics: - datname: { usage: LABEL ,description: "Database name of this index" } - relname: { usage: LABEL ,description: "Schema qualified index name" } - size: { usage: GAUGE ,description: "Total bytes of this index" } - ratio: { usage: GAUGE ,description: "Estimated bloat ratio of this index, 0~1" } pg_exporter-1.4.1/config/0910-pgbouncer_list.yml000066400000000000000000000011201523237665500214610ustar00rootroot00000000000000#==============================================================# # 0910 pgbouncer_list #==============================================================# # http://www.pgbouncer.org/usage.html#show-lists pgbouncer_list: name: pgbouncer_list desc: Pgbouncer entry list query: SHOW LISTS; ttl: 10 min_version: 10800 fatal: true tags: [ pgbouncer ] metrics: - list: { usage: LABEL ,description: "Pgbouncer internal list name" } - items: { usage: GAUGE ,description: "Number of corresponding pgbouncer object" } pg_exporter-1.4.1/config/0920-pgbouncer_database.yml000066400000000000000000000167211523237665500222700ustar00rootroot00000000000000#==============================================================# # 0920 pgbouncer_database #==============================================================# # http://www.pgbouncer.org/usage.html#show-databases pgbouncer_database_124: name: pgbouncer_database desc: Pgbouncer database stats (since 1.24) query: SHOW DATABASES; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool_size: { usage: GAUGE ,rename: reserve_pool ,description: "Maximum number of additional connections for this database" } - server_lifetime: { usage: GAUGE ,description: "The maximum lifetime of a server connection for this database" } - pool_mode: { usage: DISCARD } - load_balance_hosts: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - max_client_connections: { usage: GAUGE ,description: "Maximum number of allowed client connections for this pgbouncer instance" } - current_client_connections: { usage: GAUGE ,description: "Current number of client connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_123: name: pgbouncer_database desc: Pgbouncer database stats 1.23 query: SHOW DATABASES; ttl: 10 min_version: 12300 max_version: 12400 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - server_lifetime: { usage: GAUGE ,description: "The maximum lifetime of a server connection for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_116: name: pgbouncer_database desc: Pgbouncer database stats (1.16-1.22) query: SHOW DATABASES; ttl: 10 min_version: 11600 max_version: 12300 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_108: name: pgbouncer_database desc: Pgbouncer database stats (1.08-1.15) query: SHOW DATABASES; ttl: 10 min_version: 10800 max_version: 11600 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pg_exporter-1.4.1/config/0930-pgbouncer_stat.yml000066400000000000000000000206311523237665500214730ustar00rootroot00000000000000#==============================================================# # 0930 pgbouncer_stat #==============================================================# # http://www.pgbouncer.org/usage.html#show-stats pgbouncer_stat_124: name: pgbouncer_stat desc: Pgbouncer stats per database (since 1.24) query: SHOW STATS; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_server_assignment_count: { usage: COUNTER ,description: "Total times a server was assigned to a client" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - total_client_parse_count: { usage: COUNTER ,description: "Total number of prepared statements created by clients" } - total_server_parse_count: { usage: COUNTER ,description: "Total number of prepared statements created on a server." } - total_bind_count: { usage: COUNTER ,description: "Total number of prepared statements readied for execution by clients and forwarded to postgres" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_server_assignment_count: { usage: GAUGE ,description: "Average number of times a server as assigned to a client per second in the last stat period." } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } - avg_client_parse_count: { usage: GAUGE ,description: "Average number of prepared statements created by clients" } - avg_server_parse_count: { usage: GAUGE ,description: "Average number of prepared statements created on a server." } - avg_bind_count: { usage: GAUGE ,description: "Average number of prepared statements readied for execution by clients and forwarded to postgres" } pgbouncer_stat_123: name: pgbouncer_stat desc: Pgbouncer stats per database (1.23) query: SHOW STATS; ttl: 10 min_version: 12300 max_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_server_assignment_count: { usage: COUNTER ,description: "Total times a server was assigned to a client" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_server_assignment_count: { usage: GAUGE ,description: "Average number of times a server as assigned to a client per second in the last stat period." } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } pgbouncer_stat_108: name: pgbouncer_stat desc: Pgbouncer stats per database (1.08 - 1.22) query: SHOW STATS; ttl: 10 min_version: 10800 max_version: 12300 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } pg_exporter-1.4.1/config/0940-pgbouncer_pool.yml000066400000000000000000000232451523237665500214760ustar00rootroot00000000000000#==============================================================# # 0940 pgbouncer_pool #==============================================================# # http://www.pgbouncer.org/usage.html#show-pools pgbouncer_pool_124: name: pgbouncer_pool desc: Pgbouncer pool stats (1.24+) query: SHOW POOLS; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_active_cancel_req: { usage: GAUGE, rename: active_cancel_clients, description: "Client connections that have forwarded query cancellations to the server and are waiting for the server response." } - cl_waiting_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_active_cancel: { usage: GAUGE, rename: active_cancel_servers, description: "Server connections that are currently forwarding a cancel request" } - sv_being_canceled: { usage: GAUGE, rename: cancel_servers, description: "cancel requests have completed that were sent to cancel a query on this server" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } - load_balance_hosts: { usage: LABEL, description: "The load_balance_hosts in use" } pgbouncer_pool_118: name: pgbouncer_pool desc: Pgbouncer pool stats (1.18-1.23) query: SHOW POOLS; ttl: 10 min_version: 11800 max_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_active_cancel_req: { usage: GAUGE, rename: active_cancel_clients, description: "Client connections that have forwarded query cancellations to the server and are waiting for the server response." } - cl_waiting_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_active_cancel: { usage: GAUGE, rename: active_cancel_servers, description: "Server connections that are currently forwarding a cancel request" } - sv_being_canceled: { usage: GAUGE, rename: cancel_servers, description: "cancel requests have completed that were sent to cancel a query on this server" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pgbouncer_pool_116: name: pgbouncer_pool desc: Pgbouncer pool stats (1.16-1.17) query: SHOW POOLS; ttl: 10 min_version: 11600 max_version: 11800 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pgbouncer_pool_108: name: pgbouncer_pool desc: Pgbouncer pool stats (1.08-1.15) query: SHOW POOLS; ttl: 10 min_version: 10800 max_version: 11600 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pg_exporter-1.4.1/config/1000-pg_wait_event.yml000066400000000000000000000025231523237665500212740ustar00rootroot00000000000000#==============================================================# # 1000 pg_wait_event #==============================================================# pg_wait_event: name: pg_wait_event desc: PostgreSQL wait event sampling based on pg_wait_sampling extension query: SELECT coalesce(event_type, 'Running') AS etype, coalesce(event, 'Running') AS event, sum(count) AS count FROM pg_wait_sampling_profile GROUP BY 1,2; ttl: 10 min_version: 100000 tags: [ cluster, "extension:pg_wait_sampling" ] metrics: - etype: { usage: "LABEL" ,description: "wait event type" } - event: { usage: "LABEL" ,description: "wait event name" } - count: { usage: "COUNTER" ,description: "Total count of wait events sampled" } pg_wait_event_1s: name: pg_wait_event_1s desc: PostgreSQL wait event sampling based on pg_wait_sampling extension query: SELECT coalesce(event_type, 'Running') AS etype, coalesce(event, 'Running') AS event, count(*) FROM pg_wait_sampling_history WHERE ts BETWEEN now() - '1s'::INTERVAL AND now() GROUP BY 1,2; ttl: 10 min_version: 100000 tags: [ cluster, "extension:pg_wait_sampling" ] metrics: - etype: { usage: "LABEL" ,description: "wait event type" } - event: { usage: "LABEL" ,description: "wait event name" } - count: { usage: "GAUGE" ,description: "Number of wait events in last second" } pg_exporter-1.4.1/config/1800-pg_tsdb_hypertable.yml000066400000000000000000000024701523237665500223130ustar00rootroot00000000000000#==============================================================# # 1800 pg_tsdb_hypertable #==============================================================# # this collector reqires timescaledb extension to be installed pg_tsdb_hypertable: name: pg_tsdb_hypertable desc: TimescaleDB hypertable overview query: |- SELECT current_database() AS datname, format('%I.%I', hypertable_schema, hypertable_name) AS relname, num_dimensions AS dimensions, num_chunks AS chunks, compression_enabled::BOOLEAN::int AS compressed, hypertable_size(format('"%I"."%I"', hypertable_schema, hypertable_name)::RegClass) AS bytes FROM timescaledb_information.hypertables; ttl: 60 timeout: 2 min_version: 100000 skip: true tags: [ "extension:timescaledb", "schema:timescaledb_information" ] metrics: - datname: { usage: LABEL ,description: "database name" } - relname: { usage: LABEL ,description: "Hypertable relation name" } - dimensions: { usage: GAUGE ,description: "Number of partitioning dimensions" } - chunks: { usage: GAUGE ,description: "Total chunks of this hypertable" } - compressed: { usage: GAUGE ,description: "1 if compression enabled" } - bytes: { usage: GAUGE ,description: "Total size of hypertable in bytes" } pg_exporter-1.4.1/config/1900-pg_citus.yml000066400000000000000000000027071523237665500202730ustar00rootroot00000000000000#==============================================================# # 1900 pg_citus_node #==============================================================# # https://docs.citusdata.com/en/latest/develop/api_metadata.html#worker-node-table pg_citus_node: name: pg_citus_node desc: Citus worker coordinator node inventory query: |- SELECT CONCAT(nodename, ':', nodeport) AS node, current_database() AS datname, nodeid AS id, groupid AS group, hasmetadata::BOOLEAN::INT AS has_meta, isactive::BOOLEAN::INT AS is_active, metadatasynced::BOOLEAN::INT AS meta_synced, shouldhaveshards::BOOLEAN::INT AS have_shards FROM pg_dist_node; ttl: 60 min_version: 100000 tags: [ "extension:citus" ] metrics: - node: { usage: LABEL ,description: "nodename:port of the PostgreSQL instance" } - datname: { usage: LABEL ,description: "database name" } - id: { usage: GAUGE ,description: "auto‑generated node identifier" } - group: { usage: GAUGE ,description: "replication group id (primary + secondaries)" } - has_meta: { usage: GAUGE ,description: "1 = internal use flag set" } - is_active: { usage: GAUGE ,description: "1 = node currently accepts shards" } - meta_synced: { usage: GAUGE ,description: "1 = metadata fully synced to node" } - have_shards: { usage: GAUGE ,description: "1 = rebalancer may place shards here" } pg_exporter-1.4.1/config/2000-pg_heartbeat.yml000066400000000000000000000020741523237665500210700ustar00rootroot00000000000000#==============================================================# # 2000 heartbeat #==============================================================# # this is a example of application monitoring and predicate queries pg_heartbeat: name: pg_heartbeat desc: monitoring heartbeat in monitor.heartbeat table predicate_queries: - name: if heartbeat table exists predicate_query: | SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'monitor' AND table_name = 'heartbeat'); query: |- SELECT id AS cluster_name, extract(EPOCH FROM ts) AS ts, lsn, txid FROM monitor.heartbeat; ttl: 10 min_version: 090100 tags: [ "dbname:postgres", "schema:monitor" ] skip: true metrics: - cluster_name: { usage: LABEL ,description: "cluster_name param of this database cluster" } - ts: { usage: GAUGE ,description: "unix timestamp of the heartbeat" } - lsn: { usage: COUNTER ,description: "lsn of the heartbeat" } - txid: { usage: GAUGE ,description: "txid of the heartbeat" } pg_exporter-1.4.1/docs/000077500000000000000000000000001523237665500150215ustar00rootroot00000000000000pg_exporter-1.4.1/docs/design/000077500000000000000000000000001523237665500162725ustar00rootroot00000000000000pg_exporter-1.4.1/docs/design/histogram-pgbench.sql000066400000000000000000000007251523237665500224200ustar00rootroot00000000000000\set query_band random(1, 4) \if :query_band = 1 \set query_ms 300 \elif :query_band = 2 \set query_ms 3000 \elif :query_band = 3 \set query_ms 10000 \else \set query_ms 30000 \endif \set hold_band random(1, 4) \if :hold_band = 1 \set hold_ms 300 \elif :hold_band = 2 \set hold_ms 3000 \elif :hold_band = 3 \set hold_ms 10000 \else \set hold_ms 30000 \endif BEGIN; SELECT pg_current_xact_id(), pg_sleep(:query_ms / 1000.0); \sleep :hold_ms ms COMMIT; pg_exporter-1.4.1/docs/design/histogram.md000066400000000000000000001447641523237665500206310ustar00rootroot00000000000000# pg_exporter Snapshot Histogram Specification | Field | Value | | --- | --- | | Status | Final | | Version | 1.3 | | Last updated | 2026-07-17 | | Scope | SQL observations aggregated into classic Prometheus bucket series | | Authority | Normative design for the first pg_exporter Histogram implementation | ## 1. Purpose This document specifies the first Histogram capability for `pg_exporter`. It is the source of truth for configuration, SQL result interpretation, in-memory aggregation, Prometheus exposition, reference collectors, and test acceptance criteria. The design intentionally provides one user-facing `HISTOGRAM` usage. Version 1 does not expose separate `GAUGE_HISTOGRAM` and `COUNTER_HISTOGRAM` usages. The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** are to be interpreted as normative requirements. ## 2. Decision summary Version 1 makes the following decisions: 1. `HISTOGRAM` means a **snapshot distribution rebuilt from SQL rows during one real query execution**. 2. Each non-NULL SQL value is one observation. Rows sharing the same complete label tuple belong to the same logical Histogram. 3. Histogram state MUST NOT survive a real query execution. A scrape cache hit reuses the cached snapshot and MUST NOT add observations. 4. The exporter MUST expose the logical Histogram as cumulative bucket, count, and sum **Gauge series**. It MUST NOT use `prometheus.NewConstHistogram` in version 1. 5. Bucket series may increase or decrease between snapshots. PromQL consumers MUST NOT apply `rate()`, `irate()`, `increase()`, or reset logic to them. 6. Version 1 supports raw observations only. SQL-provided pre-aggregated buckets, counter Histograms, formal OpenMetrics GaugeHistogram exposition, Prometheus Native Histograms, and OTLP Histograms are out of scope. 7. A query execution that contains Histogram columns is published atomically. No scalar or Histogram component from a failed execution may be emitted. This is a deliberate semantic contract, not an assertion that counter and gauge Histograms are generally interchangeable. All initial PostgreSQL use cases describe the current population and therefore have gauge-like temporal behavior. ## 3. Rationale The current reference use cases are the per-database distributions of: 1. open transaction age, over every client backend that holds an `xact_start`; 2. idle-in-transaction age, over the idle-in-transaction subset of the same backends. Every population can shrink, disappear, or move between buckets. For example, an open transaction disappears when it commits or rolls back, while a long-running transaction moves from smaller age buckets to larger age buckets. These values are not monotonic across scrapes. The Go `prometheus.Collector` interface does not require separate collector implementations for counter and gauge Histograms. The row grouping and bucket aggregation algorithm is identical. The distinction matters at the temporal and exposition layers. `client_golang` v1.23.2 has no high-level constant GaugeHistogram constructor. `prometheus.NewConstHistogram` produces a counter-like Histogram family. It can render a correct instant distribution, but it assigns misleading counter semantics to a snapshot that may decrease. Version 1 therefore emits explicit Gauge series and accepts that the metric metadata is not a formal GaugeHistogram family. ## 4. Terminology **Observation** : One finite numeric value returned by SQL for a configured Histogram column. **Label tuple** : The ordered values of every `LABEL` column in the query, after existing label renaming and casting rules are applied. **Logical Histogram** : The distribution for one Histogram column and one label tuple during one real SQL execution. **Finite bucket** : A configured inclusive upper boundary. The `+Inf` bucket is generated by the exporter and is never configured. **Snapshot** : The complete logical Histogram result produced by one real SQL execution. A cached response is the same snapshot, not a new observation period. ## 5. Configuration contract ### 5.1 Column definition The canonical version 1 syntax reuses the existing `Column.Bucket` field and its singular YAML key: ```yaml - seconds: usage: HISTOGRAM bucket: [1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000] description: Open transaction age snapshot in seconds ``` `usage` remains case-insensitive during parsing and MUST be normalized to `HISTOGRAM`. The existing options have these meanings: - `name` and `rename` determine the logical base metric name in the same way as Gauge and Counter columns. - `scale` is applied to the SQL value before bucket assignment and summation. As with scalar columns, timestamp and boolean values are exempt from `scale`. - `default`, when explicitly configured, converts SQL NULL to one observation with the configured default value. The default is multiplied by `scale`, matching the existing scalar metric behavior. Without `default`, NULL is ignored. - `description` is the common help-text prefix for derived series. ### 5.2 Bucket validation A Histogram column MUST configure at least one finite bucket. Configured buckets MUST: - be valid finite `float64` values; - be strictly increasing; - contain no duplicates; - contain neither `NaN` nor positive or negative infinity; - omit `+Inf`, which is generated automatically; - use the final exported unit after `scale` has been applied to observations. Invalid Histogram configuration MUST fail configuration parsing. It MUST NOT be deferred until scrape time. Only finite upper boundaries are configured. The first classic bucket has the implicit mathematical interval `(-Inf, b0]`, where `b0` is the first configured boundary. Users MUST NOT configure `-Inf`; for finite observations an `le="-Inf"` bucket would always be empty and carries no information. Values below `b0`, including negative values, are counted in the first bucket. For a domain that legitimately contains signed observations, users SHOULD add finite negative boundaries appropriate to the domain and a `0` boundary, for example `[-100, -10, -1, 0, 1, 10, 100]`. This preserves useful resolution on both sides of zero; it does not change the implicit lower edge. The reference PostgreSQL observations are non-negative and are clamped in SQL. ### 5.3 Query column classification `HISTOGRAM` columns are metric columns. A query is valid when it defines at least one `GAUGE`, `COUNTER`, or `HISTOGRAM` column. The existing query-level label model remains unchanged: every logical metric in one query uses the same ordered label names. If two Histograms require different labels, they MUST be placed in different query definitions. ### 5.4 Reserved and derived names For a logical base name ``, version 1 derives: - `_bucket{le="..."}`; - `_count`; - `_sum`. Within one query definition that contains a Histogram: - `le` MUST NOT be a query label after label rename; - derived names MUST be valid Prometheus metric names; - derived names MUST NOT collide with any scalar or derived metric family in that query after rename; - two Histogram columns MUST NOT resolve to the same base name after rename; - help text for all instances of each derived family MUST be identical. Name and query-label conflicts MUST be rejected during per-query configuration validation. Identical derived families MAY appear in mutually exclusive version branches when type, help text, label names, and bucket boundaries are identical. A runtime constant label named `le` conflicts when any parsed query contains a Histogram. This MUST be rejected during exporter creation and configuration reload by the validation path that has both constant labels and parsed queries. It cannot be validated by `ParseConfig` alone. ## 6. SQL result contract ### 6.1 Raw observations Version 1 accepts only raw observations. For each result row: 1. label columns form the row's label tuple; 2. each Histogram column independently contributes zero or one observation; 3. a NULL Histogram value contributes no observation unless `default` is set; 4. a finite numeric value contributes exactly one observation after scaling. Multiple Histogram columns MAY be returned by one SQL query. They are grouped independently but share the query's label tuple. ### 6.2 Invalid values An observation that cannot be converted to a finite `float64` is a collection error. The collector MUST discard the entire new query result, including any scalar Gauge or Counter metrics already built from earlier rows, and report the query as failed. It MUST NOT expose a partially updated Histogram snapshot or a mixture of new scalar metrics and failed Histogram metrics. All scalar and Histogram component metrics for a query containing Histograms MUST therefore be buffered outside `q.result` until row iteration, row-error checking, value validation, and Histogram materialization all succeed. Only then may the new result be assigned to `q.result`. The version 1 implementation applies the same pending-result publication path to scalar-only queries. This is a deliberate compatibility hardening: a mid-iteration driver error no longer exposes a partial scalar prefix, and metric-construction errors are reported instead of panicking. It does not change successful scalar metric names, types, labels, or values. Negative observations are valid at the generic aggregation layer. Reference time-age collectors MUST clamp or filter impossible negative values in SQL. ### 6.3 Missing columns A configured Histogram column missing from the SQL result is a collection error. A missing Histogram column MUST NOT silently produce an empty distribution. ### 6.4 Empty populations A label tuple with zero valid observations for a Histogram is not emitted. Version 1 does not synthesize empty label tuples that are absent from SQL. Reference SQL MAY generate explicit population rows when stable zero-valued series are required, but NULL still contributes no observation. ### 6.5 Unsupported inputs Version 1 does not accept: - SQL arrays of bucket counts; - one row per bucket boundary; - weighted observations; - cumulative counts supplied by SQL; - delta windows; - quantiles supplied by SQL. These inputs require a separate, future input-layout contract. ## 7. Aggregation algorithm ### 7.1 Group identity The aggregation key is the pair: ```text (Histogram column identity, complete ordered label tuple) ``` The label tuple MUST use a collision-safe representation. A plain delimiter join is not sufficient because label values may contain the delimiter. ### 7.2 Accumulator Each logical Histogram accumulator contains: ```text labels ordered label values count uint64 observation count sum float64 observation sum bucketCount one uint64 count for each configured finite upper boundary ``` `bucketCount[i]` is cumulative and counts all observations `<= bucket[i]`. An implementation MAY store disjoint bucket populations internally, but it MUST convert them to cumulative values before exposition. ### 7.3 Observation assignment For every finite observation `v`: 1. increment `count`; 2. add `v` to `sum`; 3. find the first configured boundary `b` for which `v <= b`; 4. increment that bucket and every larger cumulative bucket. An implementation SHOULD use binary search plus either direct cumulative updates or a final prefix sum. Exact-boundary observations belong to that boundary's bucket. ### 7.4 Determinism Emission order MUST be deterministic for tests and human-readable output: 1. configured finite boundaries in ascending order; 2. generated `+Inf` bucket; 3. count; 4. sum. Logical Histogram groups SHOULD be emitted in deterministic metric-name and label-tuple order where practical. ## 8. Prometheus exposition ### 8.1 Gauge bucket layout Version 1 exposes each component with `prometheus.GaugeValue`: ```text # HELP pg_xact_age_seconds_bucket Open transaction age snapshot in seconds (cumulative bucket) # TYPE pg_xact_age_seconds_bucket gauge pg_xact_age_seconds_bucket{datname="app",le="10"} 7 pg_xact_age_seconds_bucket{datname="app",le="30"} 11 pg_xact_age_seconds_bucket{datname="app",le="+Inf"} 12 # HELP pg_xact_age_seconds_count Open transaction age snapshot in seconds (observation count) # TYPE pg_xact_age_seconds_count gauge pg_xact_age_seconds_count{datname="app"} 12 # HELP pg_xact_age_seconds_sum Open transaction age snapshot in seconds (observation sum) # TYPE pg_xact_age_seconds_sum gauge pg_xact_age_seconds_sum{datname="app"} 187.4 ``` The `+Inf` bucket value MUST equal `_count`. The exporter MUST create separate descriptors for bucket, count, and sum. The bucket descriptor has the query labels followed by the generated `le` label. Constant labels apply to all three derived families. Finite `le` label values MUST be formatted with: ```go strconv.FormatFloat(boundary, 'g', -1, 64) ``` The generated infinite boundary MUST use the literal `+Inf`. This formatting is part of the public series identity and MUST be covered by golden tests. Prometheus quantile interpolation has a separate convention from bucket storage. When the first finite upper boundary is positive and a quantile falls in that first bucket, `histogram_quantile()` assumes `0` as the interpolation lower bound. It does not treat the first bucket's storage interval as `[0, b0]`, nor does it require an exported `-Inf` bucket. For a signed domain whose first upper boundary is non-positive, Prometheus returns that upper boundary when the quantile falls in the lowest bucket rather than extrapolating toward negative infinity. Consumers MUST account for this lowest-bucket rule; finite negative boundaries and an explicit zero boundary are therefore important. ### 8.2 Deliberate metadata limitation Version 1 does not emit: ```text # TYPE histogram ``` and does not emit: ```text # TYPE gaugehistogram ``` The first would misrepresent snapshot buckets as counters. The second requires an OpenMetrics-aware gatherer and protocol negotiation not provided by the current `client_golang` collector path. The Gauge bucket layout preserves correct temporal behavior and remains usable with classic `histogram_quantile()` because Prometheus evaluates classic bucket series by the `le` label after ingestion. Prometheus lint tools may warn that Gauge families use the reserved Histogram suffixes `_bucket`, `_count`, and `_sum`. Version 1 accepts this warning as the cost of preserving familiar bucket queries without falsely declaring a counter-like Histogram. It is not a scrape or PromQL error. Documentation and CI MUST NOT claim that this layout passes Histogram-specific promlint rules. ### 8.3 Cardinality For `B` configured finite boundaries and `L` populated label tuples, a logical Histogram emits: ```text (B + 3) * L ``` series: `B` finite buckets, one `+Inf` bucket, one count, and one sum. Reference collectors MUST use low-cardinality grouping labels. They MUST NOT use `pid`, raw query text, relation name, or other per-object identity labels. ### 8.4 Internal scrape metrics The query result-size metric SHOULD count the actual number of emitted Prometheus component metrics. With the Gauge bucket layout, each bucket, count, and sum is already one `prometheus.Metric`, so no hidden expansion adjustment is required. ## 9. Cache, reload, and concurrency semantics Histogram collection reuses the existing query lifecycle: - A real SQL execution creates a new empty aggregation map. - Successful completion atomically replaces the cached result with the fully buffered scalar and Histogram component metrics. - A failed execution leaves an empty result for that execution and exposes no partial new result. Atomic result publication is a new version 1 guarantee; existing scalar row processing does not already provide it. - A TTL cache hit sends the cached component metrics unchanged. - A cache hit MUST NOT observe the cached SQL rows again. - Configuration reload discards prior collector instances and Histogram snapshots through the existing re-plan lifecycle. - Existing collector locks protect Histogram state in the same way as scalar metric results. No background sampler, ticker, persistent state, scrape deduplication, or cross-scrape accumulation is permitted in version 1. The existing collector updates its last-execution timestamp after a real attempt, including a failed attempt. Version 1 retains that behavior: a failed non-fatal Histogram query caches the empty result until its TTL expires. During that interval, absence means "no successful snapshot is available", not necessarily an empty PostgreSQL population. Operators MUST use the existing query error self-metrics to distinguish collection failure from absence. A future retry-on-error policy requires a separate lifecycle change. ## 10. Reference PostgreSQL collectors Version 1.3 is accepted only when it supports the `pg_xact_age` reference query family and its two logical Histograms. ### 10.1 Open transaction age snapshot Histograms The compact `pg_xact_age` namespace makes the exported names suitable for routine dashboard and alert use. Its execution controls are normative: ```yaml pg_xact_age: name: pg_xact_age ttl: 10 min_version: 100000 tags: [cluster] ``` The `cluster` tag is required because `pg_stat_activity` is cluster-wide. It prevents auto-discovered database connections from emitting duplicate series. The minimum version is PostgreSQL 10 because the reference SQL uses `backend_type`. PostgreSQL 10+ reference SQL: ```sql SELECT datname, greatest(0, extract(epoch FROM now() - xact_start)) AS seconds, CASE WHEN state LIKE 'idle in transaction%' THEN greatest(0, extract(epoch FROM now() - xact_start)) END AS idle_seconds FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND backend_type = 'client backend' AND datname IS NOT NULL AND xact_start IS NOT NULL; ``` The monitoring role MUST be able to see activity details for other sessions; membership in `pg_monitor` (or equivalent `pg_read_all_stats` privileges) is the reference deployment requirement. The WHERE clause filters the nullable `xact_start` timestamp at the source, so backends without an open transaction (or with privilege-masked values) are excluded instead of becoming false zero-valued observations, and the majority idle population is never fetched. Required label: ```text datname ``` The reference collector MUST NOT label by `pid`, `usename`, `application_name`, `query_id`, `wait_event`, `state`, or query text. State semantics are carried by the two column filters rather than by a label: `seconds` observes every open transaction that holds an `xact_start`, and `idle_seconds` observes the idle-in-transaction subset. The `_count` of the `seconds` family therefore doubles as the current open-transaction count per database. ### 10.2 Open transaction age buckets The two Histograms observe the same quantity, the age in seconds of an open transaction measured from `xact_start`, over two nested populations. They use a `1, 3, 10` logarithmic grid that starts at `1 s`: ```yaml - seconds: usage: HISTOGRAM bucket: [1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000] description: Open transaction age snapshot in seconds - idle_seconds: usage: HISTOGRAM bucket: [1, 3, 10, 30, 100, 300, 1000, 3000] description: Idle-in-transaction age snapshot in seconds ``` The grid is a **distribution resolution lattice**, not a list of operating policies. Within each decade the finite upper bounds are `1` and `3` times a power of ten, so adjacent ratios alternate between `3` and approximately `3.33`. The `seconds` family spans `1 s` to `100000 s` (about `27.8 h`), uses 11 finite boundaries, and emits 14 series per populated database including `+Inf`, count, and sum. The `idle_seconds` family caps at `3000 s` (about `50 min`), uses 8 finite boundaries, and emits 11 series per populated database. Buckets deliberately start at `1 s`. These are **snapshot** Histograms sampled once per real execution, so they are length-biased by the inspection paradox: a short-lived object is unlikely to be caught by a 10-second-TTL snapshot, and sub-second buckets would carry almost no observations. The first bucket therefore covers `(-Inf, 1]`, and SQL clamps observations to be non-negative. The two families use different top boundaries on purpose. Open transaction age extends to `100000 s` so that long batch jobs and bloat-forensics cases remain visible band by band. Idle-in-transaction age caps at `3000 s` because an idle transaction older than roughly `50 min` is uniformly critical: its exact age no longer changes the response, and it is counted in the `+Inf` bucket. The worst individual idle-in-transaction age remains visible through the existing `pg_activity_max_tx_duration` Gauge. The `idle_seconds` population is a strict subset of the `seconds` population. Every idle-in-transaction backend also holds an open transaction, so for every database and boundary `idle_seconds_bucket <= seconds_bucket`. The `idle_seconds` filter uses `state LIKE 'idle in transaction%'`, which includes the `idle in transaction (aborted)` variant. These observations are **live ages**, sampled from transactions that still exist at collection time. They are not completed-transaction latency distributions. Long-lived transactions are more likely to appear in repeated snapshots, so statistical interpretation is length-biased. An instant quantile answers a question about the current live population only. The right way to read these Histograms is as a **bank of count-over-threshold gauges**. For each boundary `T`, `_count` minus the cumulative bucket at `T` is the number of backends whose open (or idle) transaction currently exceeds `T`. They answer "how many backends are older than `T` right now" for a lattice of thresholds, rather than describing a latency SLO. Dashboards SHOULD treat `le` as numeric and use a logarithmic value axis for distribution plots; Section 11 gives the count-over-threshold PromQL form. The existing `pg_activity` count and maximum-duration Gauges remain unchanged. The `pg_xact_age` Histogram collector is additive and does not replace existing series. ## 11. PromQL contract ### 11.1 Instant quantile Current p95 open transaction age by database: ```promql histogram_quantile( 0.95, sum by (datname, le) ( pg_xact_age_seconds_bucket ) ) ``` ### 11.2 Population above a threshold These Histograms are best read as a bank of count-over-threshold gauges. Current open transactions older than `3000 s` (about `50 min`), an exact boundary: ```promql pg_xact_age_seconds_count - ignoring(le) pg_xact_age_seconds_bucket{le="3000"} ``` The same shape over the idle-in-transaction subset answers "how many backends are stuck idle in transaction beyond a threshold", here `300 s`: ```promql pg_xact_age_idle_seconds_count - ignoring(le) pg_xact_age_idle_seconds_bucket{le="300"} ``` `60 s` is a common policy threshold but not a boundary in this grid. The Histogram cannot provide an exact `> 60 s` population without interpolation. Consumers that require that exact threshold SHOULD use a maximum-age or dedicated threshold Gauge rather than matching a nonexistent `le="60"` series. Consumers MUST use the boundary string actually exposed by the exporter's formatting for exact matchers. The current reference boundaries are integers that render as plain decimals such as `3000` and `100000`; fractional or very large boundaries in other configurations may render in scientific notation such as `1e+09`, and consumers MUST match the exact exposed string. ### 11.3 Snapshot mean Current mean live open transaction age: ```promql sum by (datname) (pg_xact_age_seconds_sum) / sum by (datname) (pg_xact_age_seconds_count) ``` Consumers SHOULD guard division by zero or absent populations. ### 11.4 Prohibited queries The following forms are semantically invalid for version 1 Histogram series: ```promql rate(pg_xact_age_seconds_bucket[5m]) increase(pg_xact_age_seconds_count[1h]) irate(pg_xact_age_idle_seconds_bucket[5m]) ``` Recording rules and dashboards MUST NOT treat version 1 bucket, count, or sum families as counters. ## 12. Error handling and observability Configuration errors fail load with the query branch and column name in the message. Collection errors follow the existing query error and fatality behavior. A Histogram-specific error SHOULD include: - query name; - Histogram column name; - label tuple when safely printable; - failure category, such as missing column, cast failure, or non-finite value. The implementation MUST NOT log raw query text or sensitive label values merely to diagnose Histogram aggregation. No new self-metrics are required for version 1. Existing query duration, execution count, error count, cache hit count, and emitted metric count are sufficient. ## 13. Implementation boundaries The expected implementation surfaces are: - `exporter/column.go`: enable `HISTOGRAM`; retain `Bucket []float64`; - `exporter/config.go`: classify Histogram columns and validate buckets and derived names; - `exporter/query.go`: include logical Histograms in query metric metadata; - `exporter/collector.go`: aggregate observations and emit component Gauges; - `exporter/utils.go`: add or use a Histogram conversion path that distinguishes NULL-without-default from invalid/non-finite input; the existing `castFloat64` returns `NaN` for both and is not sufficient by itself; - `exporter/validate_labels.go`: reject runtime constant label `le` when any parsed query contains a Histogram, using the existing validation call sites during exporter creation and reload; - `config/0000-doc.yml` and `README.md`: document the implemented syntax and snapshot PromQL contract; - `config/0450-pg_xact_age.yml`: provide the two reference open-transaction-age snapshot Histograms; - focused unit and lifecycle tests. Version 1 SHOULD NOT require changes to the `Server`, `Exporter`, registry, HTTP handler, or exposition negotiation path. ## 14. Test and acceptance criteria ### 14.1 Configuration tests Tests MUST cover: - case-insensitive `HISTOGRAM` usage normalization; - acceptance of Histogram-only queries; - empty bucket rejection; - unsorted and duplicate bucket rejection; - `NaN`, `+Inf`, and `-Inf` bucket rejection; - derived metric and `le` label collision rejection; - rename, scale, and default parsing; - default multiplied by scale, matching scalar behavior; - explain/YAML metadata showing configured buckets. ### 14.2 Aggregation tests Tests MUST cover: - exact-boundary inclusion with `<=` semantics; - values below the first and above the last finite bucket; - generated `+Inf` bucket equal to count; - correct count and sum; - multiple label tuples; - collision-safe label tuple identity; - multiple Histogram columns in one row set; - NULL ignored without default; - NULL observed with default; - scale applied before bucket assignment; - invalid and non-finite observations failing the new snapshot; - one invalid Histogram observation discarding the entire mixed scalar and Histogram query result; - deterministic output order. ### 14.3 Exposition tests Golden gather tests MUST verify: - bucket, count, and sum are all `GAUGE` families; - finite buckets and `+Inf` are present; - finite `le` values use the specified Go `g` formatting and `+Inf` is literal; - `le` is appended without disturbing configured label order; - help strings and constant labels are correct; - no base `HISTOGRAM` family is emitted; - emitted metric count includes every component series. ### 14.4 Lifecycle tests Tests MUST verify: - a real execution rebuilds the snapshot from zero; - a cache hit returns identical metrics without accumulation; - a smaller later population decreases buckets correctly; - a query failure never exposes a partial replacement; - a failed execution caches absence until TTL expiry under the existing last-execution timestamp policy; - reload discards old Histogram collectors and cached snapshots; - concurrent collection remains race-free under the existing lock model. ### 14.5 Reference collector acceptance The implementation is complete only when: - the two `pg_xact_age` Histograms gather from a supported PostgreSQL instance; - the `pg_xact_age` collector runs once with `tags: [cluster]` under auto-discovery and emits no duplicate series; - `seconds` and `idle_seconds` each expose their configured boundaries, and the idle-in-transaction population is a strict subset of the open-transaction population for every database and boundary; - `seconds` observes every client backend that holds an `xact_start`, `idle_seconds` observes only the `idle in transaction%` subset, and each is absent for a database with no such backend; - direct `histogram_quantile()` returns plausible instant values; - threshold subtraction equals the independently queried SQL count; - existing Gauge and Counter metric names and values remain unchanged; - existing tests and race tests pass. ## 15. Non-goals Version 1 explicitly does not provide: - separate GaugeHistogram and CounterHistogram configuration types; - cross-scrape Histogram accumulation; - completed-query latency distribution; - exporter-managed event deduplication; - SQL pre-aggregated bucket ingestion; - formal OpenMetrics `gaugehistogram` metadata; - Prometheus Native Histograms; - OTLP Histogram or ExponentialHistogram export; - exemplars; - dynamic or automatically inferred bucket boundaries; - exporter-computed quantiles; - high-cardinality per-PID, per-query, or per-relation Histogram labels; - PostgreSQL 9.x compatibility for the `pg_xact_age` reference query. ## 16. Future evolution Future work requires a concrete data source and a separate design amendment. If PostgreSQL or an extension provides reliable pre-aggregated buckets, the configuration MAY later gain an input layout such as `observations` versus `buckets`. If monotonically cumulative data is actually available, the same logical `HISTOGRAM` usage MAY later gain `mode: cumulative`; separate usages are not required by this specification. Formal OpenMetrics GaugeHistogram exposition MAY be added behind the existing configuration through a gatherer or encoder adapter. Native and OTLP encodings MUST explicitly represent temporality or flavor and MUST NOT infer it from bucket values. None of these future possibilities changes the version 1 snapshot contract. ## 17. References - [Prometheus metric types](https://prometheus.io/docs/concepts/metric_types/) - [Prometheus histograms and summaries](https://prometheus.io/docs/practices/histograms/) - [Prometheus `histogram_quantile()`](https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile) - [Prometheus Go Collector API](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Collector) - [Prometheus Go default and exponential buckets](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#ExponentialBuckets) - [Prometheus Native Histogram specification](https://prometheus.io/docs/specs/native_histograms/) - [Grafana Heatmap](https://grafana.com/docs/grafana/latest/visualizations/panels-visualizations/visualizations/heatmap/) - [OpenMetrics GaugeHistogram specification](https://github.com/prometheus/OpenMetrics/blob/main/specification/OpenMetrics.md#gaugehistogram) - [OpenTelemetry metric data model](https://opentelemetry.io/docs/specs/otel/metrics/data-model/) - [OpenTelemetry Prometheus compatibility](https://opentelemetry.io/docs/specs/otel/compatibility/prometheus_and_openmetrics/) - [PostgreSQL `pg_stat_activity`](https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ACTIVITY-VIEW) - [PostgreSQL vacuum and freezing parameters](https://www.postgresql.org/docs/current/runtime-config-vacuum.html) - [PostgreSQL transaction ID wraparound](https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND) - [Pigsty OLTP PostgreSQL profile](https://github.com/pgsty/pigsty/blob/2d5a45f759274048de0c197829228a71d0182e5c/roles/pgsql/templates/oltp.yml) - [Pigsty `pg-vacuum` policy](https://github.com/pgsty/pigsty/blob/2d5a45f759274048de0c197829228a71d0182e5c/files/postgres/pg-vacuum) - [Pigsty PostgreSQL recording and alert rules](https://github.com/pgsty/pigsty/blob/2d5a45f759274048de0c197829228a71d0182e5c/files/victoria/rules/pgsql.yml) ## Appendix A. External review disposition ### A.1 Review identity | Field | Value | | --- | --- | | Reviewer | Claude Code 2.1.201 | | Model | `claude-fable-5` | | Effort | `xhigh` (Extra High) | | Mode | Non-interactive, read-only repository review | | Initial review session | `02e0e641-d206-4c90-bccb-d1875dbd90fe` | | Initial result | Conditional approval after required corrections | | Final verification session | `4462d556-8d70-4c14-b430-00253e3dc429` | | Final verification result | `FINAL PASS` | The reviewer read the **version 1.0** specification snapshot and inspected its referenced exporter, configuration, validation, lifecycle, and test files. It made no repository edits. Appendix A records that historical review and MUST NOT be read as a review of the version 1.1 amendment. ### A.2 Confirmed consensus The review explicitly confirmed these load-bearing decisions: - one user-facing `HISTOGRAM` usage is sufficient for version 1; - snapshot-per-real-execution semantics are correct for the then-proposed PostgreSQL populations; - bucket, count, and sum Gauge families are preferable to falsely declaring a counter-like `NewConstHistogram`; - direct `histogram_quantile()` remains valid on cumulative `le` buckets; - no cross-scrape accumulation, background sampler, or exporter-managed event deduplication should be introduced; - raw observations only, low-cardinality labels, additive reference collectors, and the `(B + 3) * L` cardinality model are appropriate for version 1; - formal GaugeHistogram, Native Histogram, OTLP, and cumulative bucket input remain future work. ### A.3 Accepted corrections The reviewed version 1.0 specification incorporated all correctness findings: 1. It treats query-atomic result publication as a new guarantee rather than an existing collector property, and discards the whole mixed query result on any Histogram observation failure. 2. It scopes derived-name collision checks to one query definition while allowing identical, compatible families across mutually exclusive version branches. 3. It assigns explicit execution scope, PostgreSQL version, and TTL controls to each reference collector. 4. It specifies stable `le` formatting with `strconv.FormatFloat` and literal `+Inf`. 5. It specifies that `default` is scaled and requires a conversion path that distinguishes ignored NULL from invalid data. 6. It places runtime constant-label conflict detection in the validation path that sees both parsed queries and constant labels. 7. It documents the inherited behavior that a failed real execution caches an empty result until TTL expiry. 8. It guards nullable activity timestamps, states the monitoring-role requirement, acknowledges promlint suffix warnings, and expands the test matrix accordingly. ### A.4 Resolved disagreements and tradeoffs There are no unresolved blocking disagreements. The reviewer left whole-query versus Histogram-only discard as an explicit choice and preferred whole-query discard. The final design adopts whole-query atomicity because it produces one coherent SQL snapshot and the implementation can buffer scalar and Histogram metrics in the same local result. The reviewer suggested placing the constant `le` check in CLI label parsing. The final design instead assigns it to `validateConstLabelConflicts`, because that path has both runtime constant labels and parsed query metadata and is already called during exporter creation and reload. Globally banning `le` in `parseConstLabels` would reject configurations that contain no Histogram. The review identified failure-cached absence as an operational tradeoff. The final design retains existing lifecycle behavior for version 1 rather than expanding scope into retry policy. It makes the behavior explicit and requires operators to use existing query error metrics. The Gauge suffix promlint warning is accepted. Avoiding it would require either misstating the series as a counter Histogram or abandoning familiar classic bucket names and PromQL. Neither is preferable for the stated version 1 scope. ### A.5 Final disposition All required review changes were resolved in version 1.0. That specification was approved as **Final** for implementation. A second read-only Fable 5 Extra High review verified each of the eight required corrections against that repository state and returned `FINAL PASS` with no remaining blocker. ## Appendix B. Version 1.1 post-review amendment ### B.1 Authority and review status Version 1.1 is a user-directed, post-review amendment. It preserves the reviewed aggregation, temporality, atomicity, exposition, cache, and validation contracts. It changes the normative PostgreSQL reference collector and its bucket policy only. The implemented amendment received a separate, read-only Claude Code review: | Item | Value | |---|---| | Reviewer | Claude Code | | Model | `claude-fable-5` | | Effort | `xhigh` (Extra High) | | Initial review session | `14632bbe-9078-4ead-9680-ef466c5d4048` | | Initial result | `FAIL`: one P1 test-coverage gap, no runtime defect | | Final verification session | `21514615-c953-4340-88f9-ec962fac7df0` | | Final verification result | `FINAL VERDICT: PASS` | The reviewer inspected the specification, implementation, tests, reference configuration, generated configuration, and cited local Pigsty policies. It also ran the exporter test suite under the race detector. It made no repository edits. ### B.2 Accepted amendments Version 1.1 makes the following authoritative changes after operational review: 1. The reference surface is reduced to one cluster-wide `pg_session` query with four logical Histograms. Lower-value reference collectors are removed from the normative acceptance set. 2. Metric names use the compact `pg_session` namespace and the SQL aliases `query_age_seconds`, `xact_age_seconds`, `age_seconds`, and `xid_age`. 3. The three time-age Histograms share a hybrid boundary set: Prometheus's familiar short-latency defaults through `10 s`, followed by DBA operational anchors through `30 d`. 4. The XID-horizon buckets combine logarithmic early-warning bands with PostgreSQL defaults, Pigsty policy, and final wraparound-risk landmarks. Those landmarks provide context for backend-held horizons; they do not turn backend age into an autovacuum trigger. 5. The classic first-bucket lower edge is explicitly documented as implicit negative infinity. Signed domains use finite negative boundaries and zero; neither reference configuration nor users configure `-Inf`. ### B.3 Review consensus, correction, and remaining disagreement The review confirmed the following version 1.1 decisions: - the PostgreSQL freeze, forced anti-wraparound autovacuum, failsafe, Pigsty, and wraparound landmarks are factually correct and version-scoped; - the document clearly distinguishes relation/tuple freeze triggers from backend-held XID horizons, for which the same values are risk landmarks only; - taking the greater non-negative age of `backend_xid` and `backend_xmin`, and omitting the observation only when both are NULL, correctly represents the oldest backend-held horizon; - the hybrid time list is operationally better justified than either pure powers of ten or a mechanical `1, 3, 10` ladder; - classic first-bucket, signed-value, `+Inf`, count, sum, metric naming, cardinality, and configuration-generation behavior agree across the spec and implementation. The initial review found one P1 gap: no test pinned a finite `le` value large enough to exercise scientific notation, although the specification makes that string part of the public series identity. At the time of the version 1.1 review, `TestHistogramAcceptanceScientificNotationLEIsStable` configured the then-production `2592000` second boundary and required the literal `le="2.592e+06"`. The focused test and the full race-enabled suite passed, and the final verification closed the P1. Version 1.2 retains the same golden-test contract with its current `3000000` second boundary. One non-blocking disagreement remains. The reviewer would start the client session-age buckets near one second because sub-second session ages carry little operational signal. This specification deliberately keeps all three second-valued Histograms on the same boundary set. The cost is extra low-value bucket series for session age; the benefit is one shared dashboard axis, one recording-rule shape, and direct comparison among query, transaction, and session populations. The bounded `(B + 3) * L` cost is accepted for version 1.1. This choice may be revisited with production cardinality and query data; it is not a correctness requirement for future incompatible collectors. The version 1.2 bucket-policy change is recorded in Appendix C. Future changes to temporality, input layout, or wire encoding still require an explicit amendment to this document. ## Appendix C. Version 1.2 logarithmic bucket amendment ### C.1 Authority and scope Version 1.2 is a user-directed amendment after reviewing how the version 1.1 boundaries behave in distribution plots. It changes only the normative `pg_session` bucket lists and their policy rationale. It does not change SQL observations, labels, snapshot temporality, Gauge exposition, aggregation, atomicity, cache behavior, or validation. Appendix B remains the historical record of the reviewed version 1.1 design. This appendix supersedes Appendix B.2 items 3 and 4 and the bucket-policy conclusion in Appendix B.3. Version 1.2 received a separate read-only formal review after implementation and live acceptance: | Item | Value | |---|---| | Reviewer | Claude Code | | Model | `claude-fable-5` | | Effort | `xhigh` (Extra High) | | Full review session | `19e03da1-f0ca-4447-b2ef-88b1a9ad9806` | | Full review result | `FINAL VERDICT: PASS` | | Final verification session | `5ef9f1f3-452f-4314-a93d-a74251b6cb86` | | Final verification result | `FINAL VERDICT: PASS` | Both reviews made no repository edits. The final verification inspected the strengthened lifecycle/error tests, exact reference-query assertions, committed pgbench workload, dependency classification, generated configuration, and the complete build, test, race, vet, tidy, formatting, and static-analysis battery. It reported no P0 or P1 findings. ### C.2 Resolution lattice versus policy overlay Version 1.1 mixed two concerns in one boundary list: - a stable measurement lattice for heatmaps, CDFs, and quantile interpolation; - exact PostgreSQL, Pigsty, and human operating thresholds. The resulting time-age ratios ranged from `2` to `7`; XID-age ratios ranged from `1.05` to `10`. On a logarithmic value axis this creates very wide and very narrow visual bands. Because a classic Histogram counts observations rather than normalizing by numeric or logarithmic bucket width, the unequal bands also make heatmap color intensity difficult to compare. Classic `histogram_quantile()` uses linear interpolation inside each bucket, so the same geometry causes uneven estimation resolution. Version 1.2 makes the Histogram buckets a stable logarithmic resolution lattice. Operational thresholds remain dashboard and alert policy. A policy value becomes an additional bucket only after a concrete requirement for an exact threshold population justifies the cardinality and visual cost. ### C.3 Normative grids and cardinality The three second-valued Histograms share the 18-boundary `1, 3, 10` grid `[0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000, 300000, 1000000, 3000000]`. Each populated label tuple emits 21 series. The XID Histogram uses the 11-boundary `1, 2, 5` grid `[1000000, 2000000, 5000000, 10000000, 20000000, 50000000, 100000000, 200000000, 500000000, 1000000000, 2000000000]` and emits 14 series per populated tuple. For one hypothetical label tuple populated in all four Histograms, the component-series count falls from version 1.1's `90` to `77`. (These are the version 1.2 grids, recorded inline; the current body documents the narrower version 1.3 grids.) The time grid favors a compact, nearly equal Log10 visual spacing across more than eight decades. The XID grid uses slightly finer spacing because the `1, 2, 5` sequence naturally retains the most useful PostgreSQL and Pigsty risk magnitudes. The lowest XID boundary is `1m`; lower ages are intentionally coalesced into the normal first band. Dashboard distribution panels SHOULD use a numeric logarithmic value axis. Existing maximum-age Gauges, database-age metrics, or a future dedicated threshold Gauge SHOULD carry exact policy alerts. A second threshold-shaped Histogram is deliberately not introduced. ### C.4 Live acceptance evidence The version 1.2 reference configuration was exercised on PostgreSQL 18.4 with an isolated database and a 60-client, 6-thread pgbench workload limited to approximately one scheduled transaction per second. Each transaction assigned a real XID, ran one `pg_sleep` selected from `0.3`, `3`, `10`, and `30` seconds, then held the transaction open with a client-side sleep from the same set. The 60-second run completed 64 transactions with zero failures. The committed workload is `docs/design/histogram-pgbench.sql`. The exact invocation was: ```bash pgbench -h 127.0.0.1 -p 5432 -U postgres \ -d pg_exporter_hist_v12 -n -c 60 -j 6 -R 1 -T 60 -P 10 -r \ --random-seed=42 -f docs/design/histogram-pgbench.sql ``` One uncached exporter snapshot contained 55 pgbench sessions grouped as 33 idle, 14 active, and 8 idle in transaction. Its non-cumulative active-query populations were `1`, `3`, `2`, and `8` in the `(0.3,1]`, `(1,3]`, `(3,10]`, and `(10,30]` second bands. The idle-in-transaction xact-age populations were `1`, `2`, and `5` in `(1,3]`, `(3,10]`, and `(10,30]`. All 22 backends holding an XID were in the first `xid_age <= 1m` band, as expected for a safe short test. The live exposition verified all 12 derived families as Gauges, complete finite boundary sets, monotonically cumulative buckets, `+Inf == count`, and the required NULL population semantics. Query age appeared only for active sessions; xact and XID age appeared only for active or idle-in-transaction sessions; ordinary idle sessions emitted session age without a false zero XID observation. After pgbench disconnected, every series for the isolated test database disappeared on the next uncached scrape, confirming replacement snapshot behavior rather than cross-scrape accumulation. The test did not attempt to manufacture million- or billion-transaction XID ages. It validates the XID family structure and first-band behavior, not every upper risk band. ## Appendix D. Version 1.3 reference-collector redesign ### D.1 Authority and scope Version 1.3 is a user-directed redesign of the normative reference collector, dated 2026-07-17. It preserves every engine contract reviewed and accepted in version 1.0: snapshot-per-real-execution semantics, Gauge bucket/count/sum exposition, `le` formatting, atomic result publication, NULL handling, cache and reload behavior, and the `(B + 3) * L` cardinality model. It changes only the reference query, its labels, its Histogram set, and its bucket policy. Appendices A, B, and C remain the historical record of versions 1.0, 1.1, and 1.2. Version 1.3 was applied in place: the body of this document (notably Sections 3, 5, 8, 10, 11, 13, and 14) already describes the version 1.3 design and remains the sole normative authority. This appendix records what changed relative to version 1.2 and why. Unlike Appendices A–C it does not record a separate formal read-only review session. ### D.2 Accepted changes Version 1.3 makes the following authoritative changes: 1. The reference collector `pg_session` (`config/0415-pg_session.yml`) is replaced by `pg_xact_age` (`config/0450-pg_xact_age.yml`). The execution controls are unchanged: `ttl: 10`, `min_version: 100000`, `tags: [cluster]`. 2. The four `pg_session` Histograms (`query_age_seconds`, `xact_age_seconds`, `age_seconds`, and `xid_age`) are replaced by two Histograms over open transaction age: `seconds` over every client backend that holds an `xact_start`, and `idle_seconds` over the idle-in-transaction subset. The `_count` of `seconds` doubles as the per-database open-transaction count. 3. The `state` label is removed. State semantics move into the two column `CASE` filters, cutting cardinality while preserving the idle-in-transaction view. 4. Buckets start at `1 s`. The `seconds` family uses the 11-boundary `1, 3, 10` grid `[1 … 100000]` (about `27.8 h`); `idle_seconds` uses the 8-boundary grid `[1 … 3000]` (about `50 min`). ### D.3 Rationale The redesign narrows the reference surface to the one distribution that snapshot sampling can measure honestly, and reframes what the Histogram is for. - **Open transaction age is the load-bearing signal.** A long-open transaction holds back vacuum horizons, pins bloat, and blocks DDL, and its age is a live quantity that a snapshot samples correctly. `idle_seconds` isolates the idle-in-transaction subset (the most common pathological case) as a strict subset of the same population. `state LIKE 'idle in transaction%'` also captures the `(aborted)` variant. - **Active query age was dropped.** Under autocommit it nearly duplicates open-transaction age. Stuck-query detection is a max-type signal already served by `pg_activity_max_duration`, and true latency distributions belong to `pg_stat_statements`, not to a length-biased snapshot. - **Session/connection age was dropped.** Snapshot sampling systematically misses short-lived connections (length-biased sampling), so it cannot measure churn honestly. PostgreSQL 14+ `pg_stat_database` `sessions`/`session_time` counters (the `pg_db` collector) measure churn correctly, and `pg_activity_max_conn_duration` covers the oldest connection. - **Backend XID-horizon age was dropped.** Wraparound risk is a max-type signal already covered by `pg_xact` and `pg_activity`; a distribution over backend XID ages adds no actionable information beyond the current maximum. - **Sub-second buckets were dropped.** Snapshot Histograms are length-biased by the inspection paradox: a 10-second-TTL snapshot almost never catches a sub-second object, so those buckets were noise. Buckets start at `1 s`. - **The two grids differ on purpose.** Open transaction age extends to `100000 s` for batch and bloat forensics. Idle-in-transaction age caps at `3000 s` because anything older is uniformly critical, its exact value no longer changes the response, and it lands in `+Inf`; the worst individual age remains visible through `pg_activity_max_tx_duration`. - **The reframing.** These snapshot Histograms are not latency distributions. They answer "how many backends currently exceed age `T`" for a lattice of thresholds: a bank of count-over-threshold gauges. Section 11.2 gives the canonical count-over-threshold alert form built on this property. pg_exporter-1.4.1/exporter/000077500000000000000000000000001523237665500157415ustar00rootroot00000000000000pg_exporter-1.4.1/exporter/arg.go000066400000000000000000000072211523237665500170430ustar00rootroot00000000000000package exporter import ( "fmt" "os" "runtime" "github.com/alecthomas/kingpin/v2" "github.com/prometheus/exporter-toolkit/web/kingpinflag" ) var ( // exporter settings pgURL = kingpin.Flag("url", "postgres target url").Short('u').String() configPath = kingpin.Flag("config", "path to config dir or file").Short('c').String() webConfig = kingpinflag.AddFlags(kingpin.CommandLine, ":9630") constLabels = kingpin.Flag("label", "constant labels: comma separated list of label=value pair").Short('l').Default("").Envar("PG_EXPORTER_LABEL").String() serverTags = kingpin.Flag("tag", "tags,comma separated list of server tag").Default("").Short('t').Envar("PG_EXPORTER_TAG").String() disableCache = kingpin.Flag("disable-cache", "force not using cache").Default("false").Short('C').Envar("PG_EXPORTER_DISABLE_CACHE").Bool() disableIntro = kingpin.Flag("disable-intro", "disable internal/exporter self metrics (only expose query metrics)").Short('m').Default("false").Envar("PG_EXPORTER_DISABLE_INTRO").Bool() autoDiscovery = kingpin.Flag("auto-discovery", "automatically scrape all database for given server").Short('a').Default("true").Envar("PG_EXPORTER_AUTO_DISCOVERY").Bool() excludeDatabase = kingpin.Flag("exclude-database", "excluded databases when enabling auto-discovery").Short('x').Default("template0,template1,postgres").Envar("PG_EXPORTER_EXCLUDE_DATABASE").String() includeDatabase = kingpin.Flag("include-database", "included databases when enabling auto-discovery").Short('i').Default("").Envar("PG_EXPORTER_INCLUDE_DATABASE").String() exporterNamespace = kingpin.Flag("namespace", "prefix of built-in metrics, (pg|pgbouncer) by default").Short('n').Default("").Envar("PG_EXPORTER_NAMESPACE").String() failFast = kingpin.Flag("fail-fast", "fail fast instead of waiting during start-up").Short('f').Envar("PG_EXPORTER_FAIL_FAST").Default("false").Bool() connectTimeout = kingpin.Flag("connect-timeout", "connect timeout in ms, 100 by default").Short('T').Envar("PG_EXPORTER_CONNECT_TIMEOUT").Default("100").Int() // prometheus http metricPath = kingpin.Flag("web.telemetry-path", "URL path under which to expose metrics.").Short('P').Default("/metrics").Envar("PG_EXPORTER_TELEMETRY_PATH").String() // action dryRun = kingpin.Flag("dry-run", "dry run and print raw configs").Default("false").Short('D').Bool() explainOnly = kingpin.Flag("explain", "explain server planned queries").Default("false").Short('E').Bool() // logger setting logLevel = kingpin.Flag("log.level", "log level: debug|info|warn|error").Default("info").String() logFormat = kingpin.Flag("log.format", "log format: logfmt|json").Default("logfmt").String() ) // ParseArgs will parse cli args with kingpin. url and config have special treatment func ParseArgs() { kingpin.Version(fmt.Sprintf("pg_exporter %s (built with %s on %s/%s)\n", Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)) kingpin.HelpFlag.Short('h') // kingpin bool flags don't accept `--flag=false` (only `--no-flag`). // Normalize common `=true/false` forms to avoid confusing "unexpected false" errors. _ = kingpin.MustParse(kingpin.CommandLine.Parse(normalizeKingpinBoolEqualsArgs(os.Args[1:], kingpin.CommandLine.Model()))) Logger = configureLogger(*logLevel, *logFormat) logDebugf("init pg_exporter, configPath=%v constLabels=%v disableCache=%v autoDiscovery=%v excludeDatabase=%v includeDatabase=%v connectTimeout=%vms webConfig=%v metricPath=%v", *configPath, *constLabels, *disableCache, *autoDiscovery, *excludeDatabase, *includeDatabase, *connectTimeout, *webConfig.WebListenAddresses, *metricPath) *pgURL = GetPGURL() *configPath = GetConfig() } pg_exporter-1.4.1/exporter/args_normalize.go000066400000000000000000000041601523237665500213050ustar00rootroot00000000000000package exporter import ( "strconv" "strings" "github.com/alecthomas/kingpin/v2" ) // normalizeKingpinBoolEqualsArgs rewrites boolean flags passed as `--flag=false` // (or `-f=false`) into kingpin-compatible forms (`--no-flag`). // // kingpin bool flags are "presence flags" and don't accept values, which leads // to confusing parse errors like: "unexpected false, try --help". // // This function is intentionally conservative: it only rewrites flags that are // known boolean flags in the provided kingpin model. func normalizeKingpinBoolEqualsArgs(args []string, model *kingpin.ApplicationModel) []string { if len(args) == 0 || model == nil || model.FlagGroupModel == nil { return args } boolFlags := make(map[string]struct{}) shortToLong := make(map[byte]string) for _, f := range model.FlagGroupModel.Flags { if f == nil || !f.IsBoolFlag() { continue } boolFlags[f.Name] = struct{}{} if f.Short != 0 && f.Short <= 0x7f { // only ASCII shorts are relevant to CLI parsing shortToLong[byte(f.Short)] = f.Name } } if len(boolFlags) == 0 { return args } out := make([]string, 0, len(args)) for _, arg := range args { // Long form: --flag=false if strings.HasPrefix(arg, "--") { name, val, ok := strings.Cut(arg[2:], "=") if ok { if _, isBool := boolFlags[name]; isBool { if b, err := strconv.ParseBool(val); err == nil { if b { out = append(out, "--"+name) } else { out = append(out, "--no-"+name) } continue } } } out = append(out, arg) continue } // Short form: -f=false (only single short flag) if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") { name, val, ok := strings.Cut(arg[1:], "=") if ok && len(name) == 1 { if long, exists := shortToLong[name[0]]; exists { if _, isBool := boolFlags[long]; isBool { if b, err := strconv.ParseBool(val); err == nil { if b { out = append(out, "-"+name) } else { out = append(out, "--no-"+long) } continue } } } } out = append(out, arg) continue } out = append(out, arg) } return out } pg_exporter-1.4.1/exporter/args_normalize_test.go000066400000000000000000000036311523237665500223460ustar00rootroot00000000000000package exporter import ( "reflect" "testing" "github.com/alecthomas/kingpin/v2" ) func TestNormalizeKingpinBoolEqualsArgs_Long(t *testing.T) { app := kingpin.New("test", "") app.Flag("auto-discovery", "").Short('a').Default("true").Bool() app.Flag("disable-cache", "").Short('C').Default("false").Bool() app.Flag("log.level", "").Default("info").String() tests := []struct { in []string want []string }{ {[]string{"--auto-discovery=false"}, []string{"--no-auto-discovery"}}, {[]string{"--auto-discovery=true"}, []string{"--auto-discovery"}}, {[]string{"--disable-cache=false"}, []string{"--no-disable-cache"}}, {[]string{"--disable-cache=true"}, []string{"--disable-cache"}}, {[]string{"--log.level=debug"}, []string{"--log.level=debug"}}, {[]string{"--unknown=false"}, []string{"--unknown=false"}}, } for _, tt := range tests { got := normalizeKingpinBoolEqualsArgs(tt.in, app.Model()) if !reflect.DeepEqual(got, tt.want) { t.Fatalf("normalize(%v) = %v, want %v", tt.in, got, tt.want) } } } func TestNormalizeKingpinBoolEqualsArgs_Short(t *testing.T) { app := kingpin.New("test", "") app.Flag("auto-discovery", "").Short('a').Default("true").Bool() app.Flag("disable-cache", "").Short('C').Default("false").Bool() app.Flag("dry-run", "").Short('D').Default("false").Bool() tests := []struct { in []string want []string }{ {[]string{"-a=false"}, []string{"--no-auto-discovery"}}, {[]string{"-a=true"}, []string{"-a"}}, {[]string{"-C=false"}, []string{"--no-disable-cache"}}, {[]string{"-C=true"}, []string{"-C"}}, {[]string{"-D=false"}, []string{"--no-dry-run"}}, {[]string{"-D=true"}, []string{"-D"}}, {[]string{"-x=false"}, []string{"-x=false"}}, // unknown short } for _, tt := range tests { got := normalizeKingpinBoolEqualsArgs(tt.in, app.Model()) if !reflect.DeepEqual(got, tt.want) { t.Fatalf("normalize(%v) = %v, want %v", tt.in, got, tt.want) } } } pg_exporter-1.4.1/exporter/collector.go000066400000000000000000000450001523237665500202550ustar00rootroot00000000000000package exporter import ( "context" "database/sql" "errors" "fmt" "math" "sort" "strconv" "strings" "sync" "time" "github.com/prometheus/client_golang/prometheus" ) /* ================ Collector ================ */ type predicateCacheEntry struct { at time.Time pass bool } type histogramMetricDescriptors struct { bucket *prometheus.Desc count *prometheus.Desc sum *prometheus.Desc } // histogramAccumulator holds one SQL result-set snapshot for one histogram // column and one complete (dynamic) label tuple. Server const labels are fixed // for the lifetime of a Collector, so they are implicitly part of this group. type histogramAccumulator struct { column *Column labels []string counts []uint64 // non-cumulative counts; last element is the +Inf interval count uint64 sum float64 } // Collector holds runtime information of a Query running on a Server // It is deeply coupled with Server. Besides, it can be a collector itself type Collector struct { *Query Server *Server // It's a query, but holds a server // runtime information lock sync.RWMutex // scrape lock result []prometheus.Metric // cached metrics descriptors map[string]*prometheus.Desc // scalar column name to descriptor, built on init histogramDesc map[string]histogramMetricDescriptors cacheHit bool // indicate last scrape was served from cache or real execution predicateSkip string // if nonempty, predicate query caused skip of this scrape err error // predicate cache. Entry i caches PredicateQueries[i] if it has a positive TTL. predicateCache []predicateCacheEntry // stats lastScrape time.Time // SERVER's scrape start time (for cache window align) scrapeBegin time.Time // execution begin time scrapeDone time.Time // execution complete time scrapeDuration time.Duration // last real execution duration } // NewCollector will generate query instance from query, Injecting a server object func NewCollector(q *Query, s *Server) *Collector { instance := &Collector{ Query: q, Server: s, result: make([]prometheus.Metric, 0), } if len(q.PredicateQueries) > 0 { instance.predicateCache = make([]predicateCacheEntry, len(q.PredicateQueries)) } instance.makeDescMap() return instance } // Describe implement prometheus.Collector func (q *Collector) Describe(ch chan<- *prometheus.Desc) { q.lock.Lock() defer q.lock.Unlock() q.sendDescriptors(ch) } // Collect implement prometheus.Collector func (q *Collector) Collect(ch chan<- prometheus.Metric) { q.lock.Lock() defer q.lock.Unlock() q.scrapeBegin = time.Now() if q.cacheExpired() || q.Server.DisableCache { q.execute() q.cacheHit = false q.scrapeDone = time.Now() q.scrapeDuration = q.scrapeDone.Sub(q.scrapeBegin) q.lastScrape = q.Server.scrapeBegin } else { // serve from cache q.cacheHit = true q.scrapeDone = time.Now() } q.sendMetrics(ch) // a failed real execution intentionally leaves an empty result } // ResultSize report last scraped metric count func (q *Collector) ResultSize() int { return len(q.result) } // Error wraps query error (including error in predicate query) func (q *Collector) Error() error { return q.err } // PredicateSkip tells the last scrape skip due to predicate query and if so which predicate query caused the skip? func (q *Collector) PredicateSkip() (bool, string) { return q.predicateSkip != "", q.predicateSkip } // Duration returns last scrape duration in float64 seconds func (q *Collector) Duration() float64 { return q.scrapeDone.Sub(q.scrapeBegin).Seconds() } // CacheHit report whether last scrape was serve from cache func (q *Collector) CacheHit() bool { return q.cacheHit } // Run any predicate queries for this query. Return true only if all predicate queries pass. // As a side effect sets predicateSkip to the first predicate query that failed, using // the predicate query name if specified otherwise the index. func (q *Collector) executePredicateQueries(ctx context.Context) bool { for i, predicateQuery := range q.PredicateQueries { predicateQueryName := predicateQuery.Name if predicateQueryName == "" { predicateQueryName = fmt.Sprintf("%d", i) } q.predicateSkip = predicateQueryName msgPrefix := fmt.Sprintf("predicate query [%s] for query [%s] @ server [%s]", predicateQueryName, q.Name, q.Server.Database) // Optional predicate cache (independent of main query cache). if predicateQuery.TTL > 0 && len(q.predicateCache) == len(q.PredicateQueries) { entry := q.predicateCache[i] if !entry.at.IsZero() { ttl := time.Duration(predicateQuery.TTL * float64(time.Second)) if q.scrapeBegin.Sub(entry.at) < ttl { logDebugf("%s served from predicate cache (ttl=%vs, pass=%v)", msgPrefix, predicateQuery.TTL, entry.pass) if entry.pass { continue } // cached skip return false } } } // Execute the predicate query. logDebugf("%s executing predicate query", msgPrefix) rows, err := q.Server.QueryContext(ctx, predicateQuery.SQL) if err != nil { // If a predicate query fails that's treated as a skip, and the err // flag is set so Fatal will be respected if set. if errors.Is(err, context.DeadlineExceeded) { // timeout q.err = fmt.Errorf("%s timeout because duration %v exceed limit %v", msgPrefix, time.Since(q.scrapeBegin), q.TimeoutDuration()) } else { q.err = fmt.Errorf("%s failed: %w", msgPrefix, err) } return false } // The predicate passes if it returns exactly one row with one column // that is a boolean true. colTypes, err := rows.ColumnTypes() if err != nil { q.err = fmt.Errorf("%s failed to get column types: %w", msgPrefix, err) _ = rows.Close() return false } if len(colTypes) != 1 { q.err = fmt.Errorf("%s failed because it returned %d columns, expected 1", msgPrefix, len(colTypes)) _ = rows.Close() return false } typeName := strings.ToUpper(colTypes[0].DatabaseTypeName()) if typeName != "BOOL" && typeName != "BOOLEAN" { q.err = fmt.Errorf("%s failed because it returned a column of type %s, expect bool. consider a cast(colname as boolean) or colname::boolean in the query", msgPrefix, colTypes[0].DatabaseTypeName()) _ = rows.Close() return false } firstRow := true predicatePass := sql.NullBool{} for rows.Next() { if !firstRow { q.err = fmt.Errorf("%s failed because it returned more than one row", msgPrefix) _ = rows.Close() return false } firstRow = false err = rows.Scan(&predicatePass) if err != nil { q.err = fmt.Errorf("%s failed scanning in expected 1-row 1-column nullable boolean result: %w", msgPrefix, err) _ = rows.Close() return false } } if err = rows.Err(); err != nil { q.err = fmt.Errorf("%s failed while iterating rows: %w", msgPrefix, err) _ = rows.Close() return false } if err = rows.Close(); err != nil { q.err = fmt.Errorf("%s failed closing rows: %w", msgPrefix, err) return false } pass := predicatePass.Valid && predicatePass.Bool if predicateQuery.TTL > 0 && len(q.predicateCache) == len(q.PredicateQueries) { q.predicateCache[i] = predicateCacheEntry{at: q.scrapeBegin, pass: pass} } if !pass { // successfully executed predicate query requested a skip logDebugf("%s returned false, null or zero rows, skipping query", msgPrefix) return false } logDebugf("%s returned true", msgPrefix) } // If we get here, all predicate queries passed. q.predicateSkip = "" return true } // execute will run this query to registered server, result and err are registered func (q *Collector) execute() { // A failed refresh must never publish a prefix of the new result or retain an // old snapshot. Build the complete scrape locally and publish it only after // rows, scalar metrics, and all histogram groups have been validated. pending := q.result[:0] q.result = nil q.err = nil q.predicateSkip = "" var rows *sql.Rows var err error ctx := context.Background() if q.Timeout != 0 { // if timeout is provided, use context logDebugf("query [%s] @ server [%s] executing begin with time limit: %v", q.Name, q.Server.Database, q.TimeoutDuration()) var cancel context.CancelFunc ctx, cancel = context.WithTimeout(context.Background(), q.TimeoutDuration()) defer cancel() } else { logDebugf("query [%s] @ server [%s] executing begin", q.Name, q.Server.Database) } // check predicate queries if any if predicatePass := q.executePredicateQueries(ctx); !predicatePass { // predicateSkip and err if appropriate were set as side effects return } // main query execution rows, err = q.Server.QueryContext(ctx, q.SQL) // error handling: if query failed because of timeout or error, record and return if err != nil { if errors.Is(err, context.DeadlineExceeded) { // timeout q.err = fmt.Errorf("query [%s] timeout because duration %v exceed limit %v", q.Name, time.Since(q.scrapeBegin), q.TimeoutDuration()) } else { q.err = fmt.Errorf("query [%s] failed: %w", q.Name, err) } return } // close rows defer func(rows *sql.Rows) { _ = rows.Close() }(rows) // parsing meta: fetch column metadata for dynamic name lookup columnNames, err := rows.Columns() if err != nil { q.err = fmt.Errorf("query [%s] fail retriving rows meta: %w", q.Name, err) return } columnIndexes := make(map[string]int, len(columnNames)) // column name to index for i, n := range columnNames { columnIndexes[n] = i } nColumn := len(columnNames) colData := make([]interface{}, nColumn) colArgs := make([]interface{}, nColumn) for i := range colData { colArgs[i] = &colData[i] } if len(columnNames) != len(q.Columns) { // warn if column count not match logWarnf("query [%s] column count not match, result %d ≠ config %d", q.Name, len(columnNames), len(q.Columns)) } // A missing label invalidates the identity of every resulting series, so fail // this collector instead of substituting an empty label value. for _, labelName := range q.LabelNames { if _, found := columnIndexes[labelName]; !found { q.err = fmt.Errorf("query [%s] missing label column %s.%s in result", q.Name, q.Name, labelName) return } } // A missing scalar column preserves the historic warn-and-skip behavior. A // missing histogram column is an error because silently emitting no groups is // indistinguishable from a valid empty population. for _, metricName := range q.MetricNames { column := q.Columns[metricName] if column != nil && column.IsHistogram() { if _, found := columnIndexes[metricName]; !found { q.err = fmt.Errorf("query [%s] missing histogram column %s.%s in result", q.Name, q.Name, metricName) return } } } histograms := make(map[string]map[string]*histogramAccumulator) // scan loop: for each row, extract labels from all label columns, then generate a new metric for each metric column for rows.Next() { err = rows.Scan(colArgs...) if err != nil { q.err = fmt.Errorf("fail scanning rows: %w", err) return } // get labels, sequence matters, empty string for null labels labels := make([]string, len(q.LabelNames)) for i, labelName := range q.LabelNames { labels[i] = castString(colData[columnIndexes[labelName]]) } // get metrics, warn if column not exist for _, metricName := range q.MetricNames { if dataIndex, found := columnIndexes[metricName]; found { // the metric column is found in result column := q.Columns[metricName] if column.IsHistogram() { value, present, castErr := castHistogramFloat64(colData[dataIndex], column) if castErr != nil { q.err = fmt.Errorf("query [%s] histogram column %s.%s: %w", q.Name, q.Name, metricName, castErr) return } if !present { continue } groups := histograms[metricName] if groups == nil { groups = make(map[string]*histogramAccumulator) histograms[metricName] = groups } labelKey := encodeLabelTuple(labels) accumulator := groups[labelKey] if accumulator == nil { accumulator = &histogramAccumulator{ column: column, labels: append([]string(nil), labels...), counts: make([]uint64, len(column.Bucket)+1), } groups[labelKey] = accumulator } bucketIndex := sort.SearchFloat64s(column.Bucket, value) accumulator.counts[bucketIndex]++ accumulator.count++ accumulator.sum += value if math.IsNaN(accumulator.sum) || math.IsInf(accumulator.sum, 0) { q.err = fmt.Errorf("query [%s] histogram column %s.%s: non-finite observation sum", q.Name, q.Name, metricName) return } continue } metric, metricErr := prometheus.NewConstMetric( q.descriptors[metricName], // always find desc & column via name column.PrometheusValueType(), castFloat64(colData[dataIndex], column), labels..., ) if metricErr != nil { q.err = fmt.Errorf("query [%s] failed building metric %s.%s: %w", q.Name, q.Name, metricName, metricErr) return } pending = append(pending, metric) } else { logWarnf("missing metric column %s.%s in result", q.Name, metricName) } } } if err = rows.Err(); err != nil { q.err = fmt.Errorf("query [%s] failed while iterating rows: %w", q.Name, err) return } // Emit histogram groups in config metric order and collision-safe label-key // order. Each group is bucket(s), +Inf, count, then sum. for _, metricName := range q.MetricNames { column := q.Columns[metricName] if column == nil || !column.IsHistogram() { continue } groups := histograms[metricName] groupKeys := make([]string, 0, len(groups)) for groupKey := range groups { groupKeys = append(groupKeys, groupKey) } sort.Strings(groupKeys) for _, groupKey := range groupKeys { accumulator := groups[groupKey] metrics, metricErr := q.histogramMetrics(metricName, accumulator) if metricErr != nil { q.err = fmt.Errorf("query [%s] failed building histogram %s.%s: %w", q.Name, q.Name, metricName, metricErr) return } pending = append(pending, metrics...) } } q.result = pending q.err = nil logDebugf("query [%s] executing complete in %v, metrics count: %d", q.Name, time.Since(q.scrapeBegin), len(q.result)) } // histogramMetrics materializes a snapshot histogram as ordinary Gauge series. // It deliberately does not use prometheus.Histogram: SQL refreshes are // independent snapshots whose bucket/count/sum values may decrease. func (q *Collector) histogramMetrics(metricName string, accumulator *histogramAccumulator) ([]prometheus.Metric, error) { descriptors, found := q.histogramDesc[metricName] if !found { return nil, fmt.Errorf("missing descriptors") } result := make([]prometheus.Metric, 0, len(accumulator.column.Bucket)+3) bucketLabels := make([]string, len(accumulator.labels)+1) copy(bucketLabels, accumulator.labels) var cumulative uint64 for i, upperBound := range accumulator.column.Bucket { cumulative += accumulator.counts[i] bucketLabels[len(bucketLabels)-1] = strconv.FormatFloat(upperBound, 'g', -1, 64) metric, err := prometheus.NewConstMetric(descriptors.bucket, prometheus.GaugeValue, float64(cumulative), bucketLabels...) if err != nil { return nil, err } result = append(result, metric) } cumulative += accumulator.counts[len(accumulator.column.Bucket)] bucketLabels[len(bucketLabels)-1] = "+Inf" metric, err := prometheus.NewConstMetric(descriptors.bucket, prometheus.GaugeValue, float64(cumulative), bucketLabels...) if err != nil { return nil, err } result = append(result, metric) metric, err = prometheus.NewConstMetric(descriptors.count, prometheus.GaugeValue, float64(accumulator.count), accumulator.labels...) if err != nil { return nil, err } result = append(result, metric) metric, err = prometheus.NewConstMetric(descriptors.sum, prometheus.GaugeValue, accumulator.sum, accumulator.labels...) if err != nil { return nil, err } result = append(result, metric) return result, nil } /* ================ Collector Auxiliary ================ */ // makeDescMap will generate descriptor map from Query func (q *Collector) makeDescMap() { descriptors := make(map[string]*prometheus.Desc) histogramDescriptors := make(map[string]histogramMetricDescriptors) // rename label name if label column have rename option labelNames := make([]string, len(q.LabelNames)) for i, labelName := range q.LabelNames { labelColumn := q.Columns[labelName] if labelColumn.Rename != "" { labelNames[i] = labelColumn.Rename } else { labelNames[i] = labelColumn.Name } } // rename metric if metric column have a rename option for _, metricName := range q.MetricNames { metricColumn := q.Columns[metricName] // always found prometheusName := fmt.Sprintf("%s_%s", q.Name, metricColumn.Name) if metricColumn.Rename != "" { prometheusName = fmt.Sprintf("%s_%s", q.Name, metricColumn.Rename) } if metricColumn.IsHistogram() { bucketLabelNames := append(append([]string(nil), labelNames...), "le") histogramDescriptors[metricName] = histogramMetricDescriptors{ bucket: prometheus.NewDesc( prometheusName+"_bucket", histogramHelp(metricColumn.Desc, "bucket"), bucketLabelNames, q.Server.labels, ), count: prometheus.NewDesc( prometheusName+"_count", histogramHelp(metricColumn.Desc, "count"), labelNames, q.Server.labels, ), sum: prometheus.NewDesc( prometheusName+"_sum", histogramHelp(metricColumn.Desc, "sum"), labelNames, q.Server.labels, ), } continue } descriptors[metricColumn.Name] = prometheus.NewDesc( prometheusName, metricColumn.Desc, labelNames, q.Server.labels, ) } q.descriptors = descriptors q.histogramDesc = histogramDescriptors } func (q *Collector) sendDescriptors(ch chan<- *prometheus.Desc) { for _, desc := range q.descriptors { ch <- desc } for _, descriptors := range q.histogramDesc { ch <- descriptors.bucket ch <- descriptors.count ch <- descriptors.sum } } func histogramHelp(description, component string) string { suffix := map[string]string{ "bucket": "cumulative bucket", "count": "observation count", "sum": "observation sum", }[component] if suffix == "" { suffix = component } if description == "" { return suffix } return fmt.Sprintf("%s (%s)", description, suffix) } // cacheExpired report whether this instance needs actual execution // Note you have to use Server.scrapeBegin as "now", and set that timestamp as func (q *Collector) cacheExpired() bool { return q.Server.scrapeBegin.Sub(q.lastScrape) > time.Duration(q.TTL*float64(time.Second)) } func (q *Collector) cacheTTL() float64 { return q.TTL - q.Server.scrapeBegin.Sub(q.lastScrape).Seconds() } // sendMetrics will send cached result to ch func (q *Collector) sendMetrics(ch chan<- prometheus.Metric) { for _, metric := range q.result { ch <- metric } } pg_exporter-1.4.1/exporter/collector_schema_test.go000066400000000000000000000067271523237665500226510ustar00rootroot00000000000000package exporter import ( "database/sql/driver" "strings" "testing" "time" "github.com/prometheus/client_golang/prometheus" ) func TestCollectorMissingLabelColumnFailsAtomically(t *testing.T) { tests := []struct { name string values [][]driver.Value }{ { name: "non-empty result", values: [][]driver.Value{{"db", float64(1)}}, }, { name: "empty result", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { query := histogramTestQuery( "test_missing_label", []string{"datname"}, map[string]*Column{ "value": {Name: "value", Usage: GAUGE}, }, []string{"value"}, ) collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{ columns: []string{"wrong_label", "value"}, values: tt.values, } }, nil) // A failed refresh must discard an earlier successful snapshot. collector.result = []prometheus.Metric{ prometheus.MustNewConstMetric( collector.descriptors["value"], prometheus.GaugeValue, 1, "old", ), } collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err == nil || !strings.Contains(err.Error(), "missing label column") || !strings.Contains(err.Error(), "datname") { t.Fatalf("missing label error = %v", err) } if got := collector.ResultSize(); got != 0 { t.Fatalf("missing label published %d metrics", got) } ch := make(chan prometheus.Metric, 1) collector.sendMetrics(ch) if got := len(ch); got != 0 { t.Fatalf("missing label sent %d cached metrics", got) } }) } } func TestServerNonFatalMissingLabelDoesNotStopOtherCollectors(t *testing.T) { rowsFactory := func() driver.Rows { return &histogramTestRows{ columns: []string{"datname", "value"}, values: [][]driver.Value{{"db", float64(1)}}, } } badQuery := histogramTestQuery( "test_bad_label", []string{"missing_label"}, map[string]*Column{ "value": {Name: "value", Usage: GAUGE}, }, []string{"value"}, ) badCollector := newHistogramTestCollector(t, badQuery, rowsFactory, nil) goodQuery := histogramTestQuery( "test_good_label", []string{"datname"}, map[string]*Column{ "value": {Name: "value", Usage: GAUGE}, }, []string{"value"}, ) server := badCollector.Server goodCollector := NewCollector(goodQuery, server) server.Collectors = []*Collector{badCollector, goodCollector} server.scrapeBegin = time.Now() server.ResetStats() ch := make(chan prometheus.Metric, 2) server.collectNonFatalQueries(ch) if got := len(ch); got != 1 { t.Fatalf("non-fatal collectors emitted %d metrics, want only the good collector", got) } if badCollector.Error() == nil { t.Fatal("bad collector did not report the missing label") } if got := badCollector.ResultSize(); got != 0 { t.Fatalf("bad collector retained %d metrics", got) } if err := goodCollector.Error(); err != nil { t.Fatalf("good collector failed after non-fatal error: %v", err) } if got := goodCollector.ResultSize(); got != 1 { t.Fatalf("good collector emitted %d metrics, want 1", got) } if got := server.queryScrapeErrorCount[badCollector.Name]; got != 1 { t.Fatalf("bad collector error count = %v, want 1", got) } if got := server.queryScrapeErrorCount[goodCollector.Name]; got != 0 { t.Fatalf("good collector error count = %v, want 0", got) } if got := server.queryScrapeTotalCount[goodCollector.Name]; got != 1 { t.Fatalf("good collector scrape count = %v, want 1", got) } } pg_exporter-1.4.1/exporter/column.go000066400000000000000000000063441523237665500175740ustar00rootroot00000000000000package exporter import ( "fmt" "strconv" "strings" "github.com/prometheus/client_golang/prometheus" ) /* ================ Column ================ */ const ( DISCARD = "DISCARD" // Ignore this column (when SELECT *) LABEL = "LABEL" // Use this column as a label COUNTER = "COUNTER" // Use this column as a counter GAUGE = "GAUGE" // Use this column as a gauge HISTOGRAM = "HISTOGRAM" // Use this column as a snapshot histogram observation ) // ColumnUsage determine how to use query result column var ColumnUsage = map[string]bool{ DISCARD: false, LABEL: false, COUNTER: true, GAUGE: true, HISTOGRAM: true, } // Column holds the metadata of query result type Column struct { Name string `yaml:"name"` Usage string `yaml:"usage,omitempty"` // column usage Rename string `yaml:"rename,omitempty"` // rename column Bucket []float64 `yaml:"bucket,omitempty"` // histogram bucket Scale string `yaml:"scale,omitempty"` // scale factor Default string `yaml:"default,omitempty"` // default value Desc string `yaml:"description,omitempty"` // Parsed numeric options (filled during config parsing). scaleFactor float64 hasScale bool defaultValue float64 hasDefault bool } func (c *Column) parseNumbers() error { if c.Scale != "" { f, err := strconv.ParseFloat(c.Scale, 64) if err != nil { return fmt.Errorf("invalid scale %q: %w", c.Scale, err) } c.scaleFactor = f c.hasScale = true } if c.Default != "" { f, err := strconv.ParseFloat(c.Default, 64) if err != nil { return fmt.Errorf("invalid default %q: %w", c.Default, err) } c.defaultValue = f c.hasDefault = true } return nil } // IsHistogram reports whether this column is a HISTOGRAM value column. // Usage is normalized to uppercase during config parsing, so exact // comparison is the single predicate every path must share. func (c *Column) IsHistogram() bool { return c.Usage == HISTOGRAM } // PrometheusValueType returns column's corresponding prometheus value type func (c *Column) PrometheusValueType() prometheus.ValueType { switch strings.ToUpper(c.Usage) { case GAUGE: return prometheus.GaugeValue case COUNTER: return prometheus.CounterValue default: // it's user's responsibility to make sure this is a value column panic(fmt.Errorf("column %s does not have a valid value type %s", c.Name, c.Usage)) } } // String turns column into a one-line text representation func (c *Column) String() string { return fmt.Sprintf("%-8s %-20s %s", c.Usage, c.Name, c.Desc) } // MetricDesc will generate MetricDesc from column and additional information func (c *Column) MetricDesc(prefix string, labels []string) *MetricDesc { metricName := fmt.Sprintf("%s_%s{%s}", prefix, c.Name, strings.Join(labels, ",")) if c.Rename != "" { metricName = fmt.Sprintf("%s_%s{%s}", prefix, c.Rename, strings.Join(labels, ",")) } return &MetricDesc{ metricName, labels, c, } } // MetricDesc is generated by collector's column definition type MetricDesc struct { Name string Labels []string Column *Column } // Signature will print metric signature such as pg_db_age{datname} func (m *MetricDesc) String() string { return fmt.Sprintf("%s %-8s %s", m.Name, m.Column.Usage, m.Column.Desc) } pg_exporter-1.4.1/exporter/concurrency_test.go000066400000000000000000000044561523237665500216720ustar00rootroot00000000000000package exporter import ( "net/http" "net/http/httptest" "sync" "sync/atomic" "testing" "time" ) func newMockExporter(up bool, recovery bool) *Exporter { s := &Server{ Database: "postgres", Databases: map[string]bool{"postgres": true}, UP: up, Recovery: recovery, } s.beforeScrape = func(s *Server) error { s.UP = up s.Recovery = recovery return nil } return &Exporter{server: s} } func TestReloadAndHealthHandlersNoDeadlock(t *testing.T) { originalExporter := PgExporter defer setCurrentExporter(originalExporter) originalLogger := Logger Logger = configureLogger("error", "logfmt") defer func() { Logger = originalLogger }() e1 := newMockExporter(true, false) e2 := newMockExporter(true, true) setCurrentExporter(e1) var failed atomic.Int32 var wg sync.WaitGroup // Concurrent simulated reloads. wg.Add(1) go func() { defer wg.Done() for i := 0; i < 400; i++ { ReloadLock.Lock() if i%2 == 0 { setCurrentExporter(e2) } else { setCurrentExporter(e1) } ReloadLock.Unlock() time.Sleep(time.Millisecond) } }() // Concurrent health/status requests. for i := 0; i < 8; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 200; j++ { req := httptest.NewRequest(http.MethodGet, "/up", nil) w1 := httptest.NewRecorder() e1.UpCheckFunc(w1, req) if w1.Code != http.StatusOK && w1.Code != http.StatusServiceUnavailable { failed.Add(1) } w2 := httptest.NewRecorder() e1.PrimaryCheckFunc(w2, req) if w2.Code != http.StatusOK && w2.Code != http.StatusNotFound && w2.Code != http.StatusServiceUnavailable { failed.Add(1) } w3 := httptest.NewRecorder() e1.ReplicaCheckFunc(w3, req) if w3.Code != http.StatusOK && w3.Code != http.StatusNotFound && w3.Code != http.StatusServiceUnavailable { failed.Add(1) } w4 := httptest.NewRecorder() e1.StatFunc(w4, req) if w4.Code != http.StatusOK && w4.Code != http.StatusServiceUnavailable { failed.Add(1) } } }() } done := make(chan struct{}) go func() { wg.Wait() close(done) }() select { case <-done: case <-time.After(10 * time.Second): t.Fatal("concurrent reload/health requests timed out, possible deadlock") } if failed.Load() > 0 { t.Fatalf("unexpected HTTP status count: %d", failed.Load()) } } pg_exporter-1.4.1/exporter/config.go000066400000000000000000000245701523237665500175450ustar00rootroot00000000000000package exporter import ( "fmt" "math" "os" "path/filepath" "strings" "gopkg.in/yaml.v3" ) // GetConfig will try load config from target path func GetConfig() (res string) { // priority: cli-args > env > default settings (check exist) if res = *configPath; res != "" { logInfof("retrieve config path %s from command line", res) return res } if res = os.Getenv("PG_EXPORTER_CONFIG"); res != "" { logInfof("retrieve config path %s from PG_EXPORTER_CONFIG", res) return res } candidate := []string{"pg_exporter.yml", "/etc/pg_exporter.yml", "/etc/pg_exporter"} for _, res = range candidate { if _, err := os.Stat(res); err == nil { // default1 exist logInfof("fallback on default config path: %s", res) return res } } return "" } // ParseConfig turn config content into Query struct func ParseConfig(content []byte) (queries map[string]*Query, err error) { queries = make(map[string]*Query) if err = yaml.Unmarshal(content, &queries); err != nil { return nil, fmt.Errorf("malformed config: %w", err) } // parse additional fields for branch, query := range queries { if query == nil { return nil, fmt.Errorf("query %q is null", branch) } query.Branch = branch if query.Name == "" { query.Name = branch } if strings.TrimSpace(query.SQL) == "" { return nil, fmt.Errorf("query %q has empty SQL", branch) } if query.TTL < 0 { return nil, fmt.Errorf("query %q has negative ttl: %v", branch, query.TTL) } for i, pq := range query.PredicateQueries { if strings.TrimSpace(pq.SQL) == "" { return nil, fmt.Errorf("query %q has empty predicate_query at index %d", branch, i) } if pq.TTL < 0 { return nil, fmt.Errorf("query %q has negative predicate_queries[%d].ttl: %v", branch, i, pq.TTL) } } if len(query.Metrics) == 0 { return nil, fmt.Errorf("query %q has no metrics definition", branch) } // parse query column info columns := make(map[string]*Column, len(query.Metrics)) var allColumns, labelColumns, metricColumns []string for _, colMap := range query.Metrics { if len(colMap) == 0 { return nil, fmt.Errorf("query %q has an empty metrics entry", branch) } if len(colMap) != 1 { return nil, fmt.Errorf("query %q has invalid metrics entry with %d columns, expect exactly 1", branch, len(colMap)) } for colName, column := range colMap { // one-entry map if column == nil { return nil, fmt.Errorf("query %q has null column definition for %q", branch, colName) } if column.Name == "" { column.Name = colName } usage := strings.ToUpper(strings.TrimSpace(column.Usage)) if usage == "" { return nil, fmt.Errorf("query %q column %q has empty usage", branch, colName) } if _, isValid := ColumnUsage[usage]; !isValid { return nil, fmt.Errorf("query %q column %q has unsupported usage: %s", branch, colName, column.Usage) } column.Usage = usage if err := column.parseNumbers(); err != nil { return nil, fmt.Errorf("query %q column %q: %w", branch, colName, err) } switch column.Usage { case LABEL: labelColumns = append(labelColumns, column.Name) case GAUGE, COUNTER, HISTOGRAM: if column.IsHistogram() { if err := validateHistogramBuckets(column.Bucket); err != nil { return nil, fmt.Errorf("query %q column %q: %w", branch, colName, err) } } metricColumns = append(metricColumns, column.Name) } allColumns = append(allColumns, column.Name) if _, exists := columns[column.Name]; exists { return nil, fmt.Errorf("query %q has duplicate column name %q", branch, column.Name) } columns[column.Name] = column } } if len(metricColumns) == 0 { return nil, fmt.Errorf("query %q defines no GAUGE/COUNTER/HISTOGRAM columns", branch) } query.Columns, query.ColumnNames, query.LabelNames, query.MetricNames = columns, allColumns, labelColumns, metricColumns hasHistogram := query.HasHistogram() // Validate prometheus label names and metric names. This prevents panics at scrape time. seenLabels := make(map[string]bool, len(query.LabelNames)) for _, labelColName := range query.LabelNames { c := query.Columns[labelColName] if c == nil { return nil, fmt.Errorf("query %q missing label column %q", branch, labelColName) } lbl := c.Name if c.Rename != "" { lbl = c.Rename } if err := validatePromLabelName(lbl); err != nil { return nil, fmt.Errorf("query %q label %q: %w", branch, lbl, err) } if hasHistogram && lbl == "le" { return nil, fmt.Errorf("query %q label %q conflicts with generated Histogram bucket label %q", branch, lbl, "le") } if seenLabels[lbl] { return nil, fmt.Errorf("query %q has duplicate label name %q", branch, lbl) } seenLabels[lbl] = true } // Reserve every logical base name and every emitted family name. Reserving // Histogram bases as well keeps post-rename names unambiguous even though // version 1 emits only the derived _bucket, _count, and _sum families. seenMetrics := make(map[string]string, len(query.MetricNames)*4) for _, metricColName := range query.MetricNames { c := query.Columns[metricColName] if c == nil { return nil, fmt.Errorf("query %q missing metric column %q", branch, metricColName) } suffix := c.Name if c.Rename != "" { suffix = c.Rename } metricName := fmt.Sprintf("%s_%s", query.Name, suffix) if err := validatePromMetricName(metricName); err != nil { return nil, fmt.Errorf("query %q metric %q: %w", branch, metricName, err) } familyNames := []string{metricName} if c.IsHistogram() { familyNames = append(familyNames, metricName+"_bucket", metricName+"_count", metricName+"_sum", ) } for _, familyName := range familyNames { if err := validatePromMetricName(familyName); err != nil { return nil, fmt.Errorf("query %q metric %q derived family %q: %w", branch, metricName, familyName, err) } if previous, exists := seenMetrics[familyName]; exists { return nil, fmt.Errorf("query %q metric %q family %q conflicts with metric %q", branch, metricName, familyName, previous) } seenMetrics[familyName] = metricName } } } return } // validateHistogramBuckets validates the finite inclusive upper boundaries // configured for a snapshot Histogram. The +Inf bucket is generated at // collection time and must not appear in configuration. func validateHistogramBuckets(buckets []float64) error { if len(buckets) == 0 { return fmt.Errorf("HISTOGRAM requires at least one bucket") } for i, bucket := range buckets { if math.IsNaN(bucket) || math.IsInf(bucket, 0) { return fmt.Errorf("HISTOGRAM bucket[%d] must be finite, got %v", i, bucket) } if i > 0 && bucket <= buckets[i-1] { return fmt.Errorf("HISTOGRAM buckets must be strictly increasing: bucket[%d]=%v follows %v", i, bucket, buckets[i-1]) } } return nil } func FinalizeQueries(queries map[string]*Query, source string) error { for branch, q := range queries { if q == nil { return fmt.Errorf("query %q is null", branch) } q.Path = source // If timeout is not set, set to 100ms by default. // If timeout is set to a negative number, set to 0 (disabled). if q.Timeout == 0 { q.Timeout = 0.1 } if q.Timeout < 0 { q.Timeout = 0 } } return nil } // ParseQuery generate a single query from config string func ParseQuery(config string) (*Query, error) { queries, err := ParseConfig([]byte(config)) if err != nil { return nil, err } if len(queries) == 0 { return nil, fmt.Errorf("no query definition found") } if len(queries) > 1 { return nil, fmt.Errorf("multiple query definition found") } if err := FinalizeQueries(queries, ""); err != nil { return nil, err } for _, q := range queries { return q, nil // return the only query instance } return nil, fmt.Errorf("no query definition found") } // LoadConfig will read single conf file or read multiple conf file if a dir is given // conf file in a dir will be load in alphabetic order, query with same name will overwrite predecessor func LoadConfig(configPath string) (queries map[string]*Query, err error) { stat, err := os.Stat(configPath) if err != nil { return nil, fmt.Errorf("invalid config path: %s: %w", configPath, err) } if stat.IsDir() { // iterate conf files (non-recursive) if a dir is given files, err := os.ReadDir(configPath) if err != nil { return nil, fmt.Errorf("fail reading config dir: %s: %w", configPath, err) } logDebugf("load config from dir: %s", configPath) confFiles := make([]string, 0) for _, conf := range files { if conf.IsDir() { continue // skip subdirectories } if !(strings.HasSuffix(conf.Name(), ".yaml") || strings.HasSuffix(conf.Name(), ".yml")) { continue // skip non-yaml files } confFiles = append(confFiles, filepath.Join(configPath, conf.Name())) } // make global config map and assign priority according to config file alphabetic orders // priority is an integer range from 1 to 999, where 1 - 99 is reserved for user queries = make(map[string]*Query) var queryCount, configCount int var firstErr error for _, confPath := range confFiles { if singleQueries, err := LoadConfig(confPath); err != nil { logWarnf("skip config %s due to error: %s", confPath, err.Error()) if firstErr == nil { firstErr = err } } else { configCount++ for name, query := range singleQueries { queryCount++ if query.Priority == 0 { // set to config rank if not manually set query.Priority = 100 + configCount } queries[name] = query // so the later one will overwrite former one } } } if len(confFiles) > 0 && len(queries) == 0 { if firstErr != nil { return nil, fmt.Errorf("no valid queries loaded from config dir %s (%d yaml files), first error: %w", configPath, len(confFiles), firstErr) } return nil, fmt.Errorf("no queries loaded from config dir %s (%d yaml files)", configPath, len(confFiles)) } logDebugf("load %d of %d queries from %d config files", len(queries), queryCount, configCount) return queries, nil } // single file case: recursive exit condition content, err := os.ReadFile(configPath) if err != nil { return nil, fmt.Errorf("fail reading config file %s: %w", configPath, err) } queries, err = ParseConfig(content) if err != nil { return nil, err } if err := FinalizeQueries(queries, stat.Name()); err != nil { return nil, err } logDebugf("load %d queries from %s", len(queries), configPath) return queries, nil } pg_exporter-1.4.1/exporter/config_coverage_pg9_test.go000066400000000000000000000030301523237665500232220ustar00rootroot00000000000000package exporter import ( "testing" ) // Ensure the legacy config (legacy/config) covers PG9.1..PG9.6 without version // gaps for collectors that are supposed to work on legacy PG9.x. func TestConfigCoveragePG9(t *testing.T) { queries := loadConfigDirForTest(t, "legacy/config") byName := make(map[string][]*Query) for _, q := range queries { if q.HasTag("pgbouncer") { // PG and pgbouncer versions are in different namespaces. continue } byName[q.Name] = append(byName[q.Name], q) } versions := []int{90100, 90200, 90300, 90400, 90500, 90600} // PG9.1..PG9.6 for name, qs := range byName { minMin := 0 for i, q := range qs { if i == 0 || q.MinVersion < minMin { minMin = q.MinVersion } } for _, v := range versions { // Collectors introduced after v are allowed to have gaps for older versions. if minMin != 0 && v < minMin { continue } var appl []*Query for _, q := range qs { if q.MinVersion != 0 && v < q.MinVersion { continue } if q.MaxVersion != 0 && v >= q.MaxVersion { continue } appl = append(appl, q) } if len(appl) == 0 { t.Errorf("collector %q has no branch for server_version_num=%d", name, v) continue } if len(appl) > 1 && !branchesMutuallyExclusive(appl) { t.Errorf("collector %q has %d overlapping branches for server_version_num=%d: %v", name, len(appl), v, func() []string { out := make([]string, 0, len(appl)) for _, q := range appl { out = append(out, q.Branch) } return out }()) } } } } pg_exporter-1.4.1/exporter/config_coverage_test.go000066400000000000000000000056221523237665500224540ustar00rootroot00000000000000package exporter import ( "os" "path/filepath" "runtime" "testing" ) // loadConfigDirForTest loads a repo-relative config directory, skipping the // test when the directory is absent (e.g. in stripped source archives). func loadConfigDirForTest(t *testing.T, rel string) map[string]*Query { t.Helper() _, thisFile, _, ok := runtime.Caller(0) if !ok { t.Fatal("runtime.Caller failed") } configDir := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", filepath.FromSlash(rel))) if _, err := os.Stat(configDir); err != nil { t.Skipf("config dir not found: %s: %v", configDir, err) } queries, err := LoadConfig(configDir) if err != nil { t.Fatalf("LoadConfig(%s) failed: %v", configDir, err) } return queries } // branchesMutuallyExclusive reports whether overlapping branches for one // collector are acceptable because they split on the engine-defined // primary/replica tag pair. This is a tag-semantics check, deliberately not // keyed on any collector name: the config is user-replaceable content. func branchesMutuallyExclusive(appl []*Query) bool { if len(appl) != 2 { return false } return (appl[0].HasTag("primary") && appl[1].HasTag("replica")) || (appl[0].HasTag("replica") && appl[1].HasTag("primary")) } // Ensure the repo-bundled config/ covers PG10..PG19 without version gaps for // collectors that are supposed to work on PG10+. This is a cheap static check // (no DB required) to catch off-by-one mistakes on min/max_version splits. // It iterates whatever collectors the config defines and never assumes any // specific collector exists: the config is user-replaceable content, not a fixture. func TestConfigCoveragePG10To19(t *testing.T) { queries := loadConfigDirForTest(t, "config") byName := make(map[string][]*Query) for _, q := range queries { if q.HasTag("pgbouncer") { // PG and pgbouncer versions are in different namespaces. continue } byName[q.Name] = append(byName[q.Name], q) } for name, qs := range byName { minMin := 0 for i, q := range qs { if i == 0 || q.MinVersion < minMin { minMin = q.MinVersion } } // Collectors introduced after PG10 are allowed to have gaps for PG10-. if minMin > 100000 { continue } for v := 100000; v <= 190000; v += 10000 { // PG10..PG19 var appl []*Query for _, q := range qs { if q.MinVersion != 0 && v < q.MinVersion { continue } if q.MaxVersion != 0 && v >= q.MaxVersion { // exclude continue } appl = append(appl, q) } if len(appl) == 0 { t.Errorf("collector %q has no branch for server_version_num=%d", name, v) continue } if len(appl) > 1 && !branchesMutuallyExclusive(appl) { t.Errorf("collector %q has %d overlapping branches for server_version_num=%d: %v", name, len(appl), v, func() []string { out := make([]string, 0, len(appl)) for _, q := range appl { out = append(out, q.Branch) } return out }()) } } } } pg_exporter-1.4.1/exporter/config_merged_test.go000066400000000000000000000063331523237665500221240ustar00rootroot00000000000000package exporter import ( "bytes" "os" "path/filepath" "reflect" "runtime" "slices" "testing" ) func parseConfigDirLikeMerge(t *testing.T, dir string) map[string]*Query { t.Helper() entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("ReadDir(%s) failed: %v", dir, err) } names := make([]string, 0, len(entries)) for _, entry := range entries { if entry.IsDir() { continue } ext := filepath.Ext(entry.Name()) if ext != ".yml" && ext != ".yaml" { continue } names = append(names, entry.Name()) } slices.Sort(names) queries := make(map[string]*Query) for _, name := range names { path := filepath.Join(dir, name) content, err := os.ReadFile(path) if err != nil { t.Fatalf("ReadFile(%s) failed: %v", path, err) } parsed, err := ParseConfig(content) if err != nil { t.Fatalf("ParseConfig(%s) failed: %v", path, err) } for branch, q := range parsed { queries[branch] = q } } return queries } func readConfigDirBytesLikeMerge(t *testing.T, dir string) []byte { t.Helper() entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("ReadDir(%s) failed: %v", dir, err) } names := make([]string, 0, len(entries)) for _, entry := range entries { if entry.IsDir() { continue } if filepath.Ext(entry.Name()) == ".yml" { names = append(names, entry.Name()) } } slices.Sort(names) var out []byte for _, name := range names { path := filepath.Join(dir, name) content, err := os.ReadFile(path) if err != nil { t.Fatalf("ReadFile(%s) failed: %v", path, err) } out = append(out, content...) } return out } func TestMergedConfigsMatchSplitDirectories(t *testing.T) { _, thisFile, _, ok := runtime.Caller(0) if !ok { t.Fatal("runtime.Caller failed") } repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..")) cases := []struct { name string dir string merged string }{ { name: "current", dir: filepath.Join(repoRoot, "config"), merged: filepath.Join(repoRoot, "pg_exporter.yml"), }, { name: "legacy", dir: filepath.Join(repoRoot, "legacy", "config"), merged: filepath.Join(repoRoot, "legacy", "pg_exporter.yml"), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { splitQueries := parseConfigDirLikeMerge(t, tc.dir) mergedContent, err := os.ReadFile(tc.merged) if err != nil { t.Fatalf("ReadFile(%s) failed: %v", tc.merged, err) } if wantBytes := readConfigDirBytesLikeMerge(t, tc.dir); !bytes.Equal(mergedContent, wantBytes) { t.Fatalf("%s is not byte-for-byte generated from %s; run make conf", tc.merged, tc.dir) } mergedQueries, err := ParseConfig(mergedContent) if err != nil { t.Fatalf("ParseConfig(%s) failed: %v", tc.merged, err) } if len(splitQueries) != len(mergedQueries) { t.Fatalf("query count mismatch: split=%d merged=%d", len(splitQueries), len(mergedQueries)) } for branch, splitQuery := range splitQueries { mergedQuery, ok := mergedQueries[branch] if !ok { t.Fatalf("branch %q missing from merged config %s", branch, tc.merged) } if !reflect.DeepEqual(splitQuery, mergedQuery) { t.Fatalf("branch %q differs between split dir %s and merged config %s", branch, tc.dir, tc.merged) } } }) } } pg_exporter-1.4.1/exporter/config_style_test.go000066400000000000000000000046071523237665500220230ustar00rootroot00000000000000package exporter import ( "bufio" "bytes" "os" "path/filepath" "regexp" "runtime" "strings" "testing" ) var inlineMetricDescriptionRE = regexp.MustCompile(`^\s*-\s*[^:]+:\s*\{.*\bdescription:\s*(.+)\}\s*$`) func TestInlineMetricDescriptionsUseDoubleQuotes(t *testing.T) { _, thisFile, _, ok := runtime.Caller(0) if !ok { t.Fatal("runtime.Caller failed") } repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..")) for _, rel := range []string{"config", filepath.Join("legacy", "config")} { dir := filepath.Join(repoRoot, rel) t.Run(rel, func(t *testing.T) { entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("ReadDir(%s) failed: %v", dir, err) } for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".yml" { continue } path := filepath.Join(dir, entry.Name()) f, err := os.Open(path) if err != nil { t.Fatalf("Open(%s) failed: %v", path, err) } scanner := bufio.NewScanner(f) for lineNo := 1; scanner.Scan(); lineNo++ { line := scanner.Text() m := inlineMetricDescriptionRE.FindStringSubmatch(line) if m == nil { continue } desc := strings.TrimSpace(m[1]) if len(desc) < 2 || desc[0] != '"' || desc[len(desc)-1] != '"' { t.Errorf("%s:%d inline metric description must use double quotes: %s", path, lineNo, desc) } } if err := scanner.Err(); err != nil { _ = f.Close() t.Fatalf("Scan(%s) failed: %v", path, err) } if err := f.Close(); err != nil { t.Fatalf("Close(%s) failed: %v", path, err) } } }) } } func TestLegacySplitConfigsEndWithTwoBlankLines(t *testing.T) { _, thisFile, _, ok := runtime.Caller(0) if !ok { t.Fatal("runtime.Caller failed") } dir := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", "legacy", "config")) entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("ReadDir(%s) failed: %v", dir, err) } for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".yml" { continue } path := filepath.Join(dir, entry.Name()) data, err := os.ReadFile(path) if err != nil { t.Fatalf("ReadFile(%s) failed: %v", path, err) } trailingNewlines := len(data) - len(bytes.TrimRight(data, "\n")) if trailingNewlines != 3 { t.Errorf("%s must end with exactly two blank lines (3 trailing newlines), got %d", path, trailingNewlines) } } } pg_exporter-1.4.1/exporter/config_test.go000066400000000000000000000160321523237665500205760ustar00rootroot00000000000000package exporter import ( "os" "path/filepath" "strings" "testing" ) func TestParseConfigUsageCaseInsensitive(t *testing.T) { config := ` test_query: query: SELECT 1 AS metric, 'db' AS datname metrics: - metric: usage: gauge description: metric value - datname: usage: label description: database name ` queries, err := ParseConfig([]byte(config)) if err != nil { t.Fatalf("ParseConfig returned error: %v", err) } query, ok := queries["test_query"] if !ok { t.Fatalf("query test_query not found") } if got := query.Columns["metric"].Usage; got != GAUGE { t.Fatalf("metric usage = %s, want %s", got, GAUGE) } if got := query.Columns["datname"].Usage; got != LABEL { t.Fatalf("datname usage = %s, want %s", got, LABEL) } } func TestParseConfigInvalidUsage(t *testing.T) { config := ` bad_query: query: SELECT 1 AS metric metrics: - metric: usage: bad_usage description: metric value ` if _, err := ParseConfig([]byte(config)); err == nil { t.Fatal("ParseConfig should fail on unsupported usage") } } func TestParseConfigRejectsMultiColumnMetricsEntry(t *testing.T) { config := ` bad_query: query: SELECT 1 AS a, 2 AS b metrics: - a: usage: gauge b: usage: gauge ` if _, err := ParseConfig([]byte(config)); err == nil { t.Fatal("ParseConfig should fail when one metrics entry defines multiple columns") } } func TestParseQueryErrors(t *testing.T) { if _, err := ParseQuery(`{}`); err == nil { t.Fatal("ParseQuery should fail when no query is defined") } multi := ` q1: query: SELECT 1 AS metric metrics: - metric: usage: gauge q2: query: SELECT 2 AS metric metrics: - metric: usage: gauge ` if _, err := ParseQuery(multi); err == nil { t.Fatal("ParseQuery should fail when multiple queries are defined") } } func TestLoadConfigDirectoryPriorityAndOverride(t *testing.T) { dir := t.TempDir() f1 := filepath.Join(dir, "0100-a.yml") f2 := filepath.Join(dir, "0200-b.yml") cfg1 := ` q_common: query: SELECT 1 AS metric metrics: - metric: usage: gauge ` cfg2 := ` q_common: query: SELECT 2 AS metric metrics: - metric: usage: gauge q_extra: query: SELECT 3 AS metric metrics: - metric: usage: gauge ` if err := os.WriteFile(f1, []byte(cfg1), 0o644); err != nil { t.Fatalf("write config 1 failed: %v", err) } if err := os.WriteFile(f2, []byte(cfg2), 0o644); err != nil { t.Fatalf("write config 2 failed: %v", err) } queries, err := LoadConfig(dir) if err != nil { t.Fatalf("LoadConfig dir failed: %v", err) } if len(queries) != 2 { t.Fatalf("LoadConfig query count = %d, want 2", len(queries)) } if queries["q_common"].SQL != "SELECT 2 AS metric" { t.Fatalf("q_common should be overridden by later file, got: %s", queries["q_common"].SQL) } // 2nd config file gets default priority 102. if queries["q_common"].Priority != 102 { t.Fatalf("q_common priority = %d, want 102", queries["q_common"].Priority) } if queries["q_extra"].Priority != 102 { t.Fatalf("q_extra priority = %d, want 102", queries["q_extra"].Priority) } } func TestLoadConfigDirectoryAllInvalidReturnsError(t *testing.T) { dir := t.TempDir() bad := ` q_bad: query: SELECT 1 AS metric metrics: - metric: usage: bad_usage ` if err := os.WriteFile(filepath.Join(dir, "0100-bad.yml"), []byte(bad), 0o644); err != nil { t.Fatalf("write config failed: %v", err) } if _, err := LoadConfig(dir); err == nil { t.Fatal("LoadConfig should fail when no valid queries are loaded from a config directory") } else if !strings.Contains(err.Error(), "unsupported usage: bad_usage") { t.Fatalf("LoadConfig error %q does not preserve the first file error", err) } } func TestLoadConfigDirectorySkipsInvalidFile(t *testing.T) { dir := t.TempDir() valid := ` q_valid: query: SELECT 1 AS metric metrics: - metric: usage: gauge ` invalid := ` q_invalid: query: SELECT 1 AS metric metrics: - metric: usage: bad_usage ` if err := os.WriteFile(filepath.Join(dir, "0100-invalid.yml"), []byte(invalid), 0o644); err != nil { t.Fatalf("write invalid config failed: %v", err) } if err := os.WriteFile(filepath.Join(dir, "0200-valid.yml"), []byte(valid), 0o644); err != nil { t.Fatalf("write valid config failed: %v", err) } queries, err := LoadConfig(dir) if err != nil { t.Fatalf("LoadConfig should skip the invalid file: %v", err) } if len(queries) != 1 { t.Fatalf("LoadConfig returned %d queries, want only the valid query", len(queries)) } if _, ok := queries["q_valid"]; !ok { t.Fatal("LoadConfig did not return q_valid") } if _, ok := queries["q_invalid"]; ok { t.Fatal("LoadConfig returned q_invalid from the skipped file") } if queries["q_valid"].Priority != 101 { t.Fatalf("q_valid priority = %d, want 101 after one valid config", queries["q_valid"].Priority) } } func TestLoadConfigDirectoryAllowsDocumentationYAML(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, "0000-doc.yml"), []byte("# documentation only\n"), 0o644); err != nil { t.Fatalf("write documentation config failed: %v", err) } valid := ` q_valid: query: SELECT 1 AS metric metrics: - metric: usage: gauge ` if err := os.WriteFile(filepath.Join(dir, "0100-valid.yml"), []byte(valid), 0o644); err != nil { t.Fatalf("write valid config failed: %v", err) } queries, err := LoadConfig(dir) if err != nil { t.Fatalf("LoadConfig failed with documentation and valid YAML: %v", err) } if _, ok := queries["q_valid"]; !ok { t.Fatal("LoadConfig did not return q_valid") } } func TestLoadConfigDirectoryNoQueries(t *testing.T) { t.Run("empty directory", func(t *testing.T) { queries, err := LoadConfig(t.TempDir()) if err != nil { t.Fatalf("LoadConfig failed for an empty directory: %v", err) } if len(queries) != 0 { t.Fatalf("LoadConfig returned %d queries from an empty directory, want 0", len(queries)) } }) t.Run("only empty YAML", func(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, "0000-empty.yml"), nil, 0o644); err != nil { t.Fatalf("write empty config failed: %v", err) } if err := os.WriteFile(filepath.Join(dir, "0001-comment.yaml"), []byte("# documentation only\n"), 0o644); err != nil { t.Fatalf("write comment-only config failed: %v", err) } if queries, err := LoadConfig(dir); err == nil { t.Fatalf("LoadConfig returned %d queries from empty YAML files, want error", len(queries)) } }) } func TestGetConfigPrecedence(t *testing.T) { originConfigPath := *configPath t.Cleanup(func() { *configPath = originConfigPath }) originEnv := os.Getenv("PG_EXPORTER_CONFIG") t.Cleanup(func() { _ = os.Setenv("PG_EXPORTER_CONFIG", originEnv) }) *configPath = "/tmp/from-cli.yml" _ = os.Setenv("PG_EXPORTER_CONFIG", "/tmp/from-env.yml") if got := GetConfig(); got != "/tmp/from-cli.yml" { t.Fatalf("GetConfig CLI precedence failed: got %s", got) } *configPath = "" if got := GetConfig(); got != "/tmp/from-env.yml" { t.Fatalf("GetConfig env fallback failed: got %s", got) } } pg_exporter-1.4.1/exporter/exporter.go000066400000000000000000000743701523237665500201530ustar00rootroot00000000000000package exporter import ( "errors" "fmt" "html" "io" "net/http" "sync" "sync/atomic" "time" "github.com/prometheus/client_golang/prometheus" ) /* ================ Exporter ================ */ const ( healthStatusUnknown int32 = iota healthStatusDown healthStatusStarting healthStatusPrimary healthStatusReplica ) // Exporter implement prometheus.Collector interface // exporter contains one or more (auto-discover-database) servers that can scrape metrics with Query type Exporter struct { // config params provided from ExporterOpt dsn string // primary dsn configPath string // config file path /directory configReader io.Reader // reader to a config file, one of configPath or configReader must be set disableCache bool // always execute query when been scraped disableIntro bool // disable internal/exporter self metrics (only expose query metrics) autoDiscovery bool // discovery other database on primary server pgbouncerMode bool // is primary server a pgbouncer ? failFast bool // fail fast instead fof waiting during start-up ? excludeDatabase map[string]bool // excluded database for auto discovery includeDatabase map[string]bool // include database for auto discovery constLabels prometheus.Labels // prometheus const k=v labels tags []string // tags passed to this exporter for scheduling purpose namespace string // metrics prefix ('pg' or 'pgbouncer' by default) connectTimeout int // timeout in ms when perform server pre-check // internal status lock sync.RWMutex // export lock server *Server // primary server sLock sync.RWMutex // server map lock servers map[string]*Server // auto discovered peripheral servers queries map[string]*Query // metrics query definition // internal stats scrapeBegin time.Time // server level scrape begin scrapeDone time.Time // server last scrape done // internal metrics: global, exporter, server, query up prometheus.Gauge // cluster level: primary target server is alive version prometheus.Gauge // cluster level: postgres main server version num recovery prometheus.Gauge // cluster level: postgres is in recovery ? buildInfo prometheus.Gauge // exporter level: build information exporterUp prometheus.Gauge // exporter level: always set ot 1 exporterUptime prometheus.Gauge // exporter level: primary target server uptime (exporter itself) lastScrapeTime prometheus.Gauge // exporter level: last scrape timestamp scrapeDuration prometheus.Gauge // exporter level: seconds spend on scrape scrapeTotalCount prometheus.Counter // exporter level: total scrape count of this server scrapeErrorCount prometheus.Counter // exporter level: error scrape count // Dynamic series (auto-discovered DBs, config reload) are emitted as const // metrics on each scrape to avoid GaugeVec Reset() overhead and stale series. serverScrapeDurationDesc *prometheus.Desc // {datname} database level: last scrape duration serverScrapeTotalSecondsDesc *prometheus.Desc // {datname} database level: cumulative scrape seconds serverScrapeTotalCountDesc *prometheus.Desc // {datname} database level: total scrape count serverScrapeErrorCountDesc *prometheus.Desc // {datname} database level: cumulative fatal scrape error count queryCacheTTLDesc *prometheus.Desc // {datname,query} query cache ttl queryScrapeTotalCountDesc *prometheus.Desc // {datname,query} query level: total executions queryScrapeErrorCountDesc *prometheus.Desc // {datname,query} query level: error count queryScrapePredicateSkipCountDesc *prometheus.Desc // {datname,query} query level: predicate skip count queryScrapeDurationDesc *prometheus.Desc // {datname,query} query level: execution duration (seconds) queryScrapeMetricCountDesc *prometheus.Desc // {datname,query} query level: returned metric count queryScrapeHitCountDesc *prometheus.Desc // {datname,query} query level: cache hit count // lock-free health snapshot for high-frequency probes healthUp atomic.Bool healthRecovery atomic.Bool healthStatus atomic.Int32 healthLoopLock sync.Mutex healthLoopStop chan struct{} healthLoopDone chan struct{} } // Up will delegate aliveness check to primary server func (e *Exporter) Up() bool { return e.healthUp.Load() } // Recovery will delegate primary/replica check to primary server func (e *Exporter) Recovery() bool { return e.healthRecovery.Load() } // Status will report available status: primary|replica|starting|down|unknown func (e *Exporter) Status() string { switch e.healthStatus.Load() { case healthStatusPrimary: return `primary` case healthStatusReplica: return `replica` case healthStatusStarting: return `starting` case healthStatusDown: return `down` default: return `unknown` } } func (e *Exporter) updateHealthState(up, recovery bool) { e.updateHealthStateWithStartup(up, recovery, false) } func (e *Exporter) updateHealthStateWithStartup(up, recovery, starting bool) { e.healthUp.Store(up) if starting { e.healthRecovery.Store(false) e.healthStatus.Store(healthStatusStarting) return } e.healthRecovery.Store(up && recovery) if !up { e.healthStatus.Store(healthStatusDown) return } if recovery { e.healthStatus.Store(healthStatusReplica) return } e.healthStatus.Store(healthStatusPrimary) } func (e *Exporter) updateHealthStateFromServer() { if e.server == nil { e.healthUp.Store(false) e.healthRecovery.Store(false) e.healthStatus.Store(healthStatusUnknown) return } e.server.lock.RLock() up := e.server.UP recovery := e.server.Recovery e.server.lock.RUnlock() e.updateHealthState(up, recovery) } func (e *Exporter) probeAndUpdateHealthState() error { if e.server == nil { e.healthUp.Store(false) e.healthRecovery.Store(false) e.healthStatus.Store(healthStatusUnknown) return errors.New("primary server is nil") } up, recovery, starting, err := e.server.ProbeHealth() e.updateHealthStateWithStartup(up, recovery, starting) return err } func (e *Exporter) startHealthLoop() { e.healthLoopLock.Lock() if e.healthLoopStop != nil { e.healthLoopLock.Unlock() return } stopCh := make(chan struct{}) doneCh := make(chan struct{}) e.healthLoopStop = stopCh e.healthLoopDone = doneCh e.healthLoopLock.Unlock() go func() { defer close(doneCh) ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() // Health probing is intentionally decoupled from /metrics scraping so that: // - HTTP health handlers never block on network calls // - the exporter can recover health status even when scrapes are failing // // Keep Server.ProbeHealth cheap and log-noise-free: it runs once per second. _ = e.probeAndUpdateHealthState() for { select { case <-stopCh: return case <-ticker.C: _ = e.probeAndUpdateHealthState() } } }() } func (e *Exporter) stopHealthLoop() { e.healthLoopLock.Lock() stopCh := e.healthLoopStop doneCh := e.healthLoopDone e.healthLoopStop = nil e.healthLoopDone = nil e.healthLoopLock.Unlock() if stopCh == nil { return } close(stopCh) if doneCh != nil { <-doneCh } } // Describe implement prometheus.Collector func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { // Intentionally leave this exporter "unchecked". // // Query metrics are dynamic: // - config reload can add/remove collectors and metrics // - auto-discovery can add/remove databases // // If we emitted any descriptors here, the Prometheus registry would enforce // that Collect() only returns described metrics, which does not hold for a // dynamic exporter. Exporter-toolkit and client_golang both support this // pattern (Describe emits nothing). } // Collect implement prometheus.Collector func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.lock.Lock() defer e.lock.Unlock() if !e.disableIntro { e.scrapeTotalCount.Add(1) } e.scrapeBegin = time.Now() // scrape primary server s := e.server s.Collect(ch) // scrape extra servers if exists for _, srv := range e.IterateServer() { srv.Collect(ch) } e.scrapeDone = time.Now() if !e.disableIntro { e.lastScrapeTime.Set(float64(e.scrapeDone.Unix())) e.scrapeDuration.Set(e.scrapeDone.Sub(e.scrapeBegin).Seconds()) } s.lock.RLock() version := s.Version up := s.UP recovery := s.Recovery s.lock.RUnlock() e.updateHealthState(up, recovery) if !e.disableIntro { e.version.Set(float64(version)) if up { e.up.Set(1) if recovery { e.recovery.Set(1) } else { e.recovery.Set(0) } } else { e.up.Set(0) e.scrapeErrorCount.Add(1) } e.exporterUptime.Set(e.server.Uptime()) e.collectServerMetrics(ch) e.collectInternalMetrics(ch) } } func (e *Exporter) collectServerMetrics(ch chan<- prometheus.Metric) { servers := e.IterateServer() if e.server != nil { servers = append(servers, e.server) // append primary server to extra server list } for _, s := range servers { if s == nil { continue } s.lock.RLock() datname := s.Database scrapeDur := s.scrapeDone.Sub(s.scrapeBegin).Seconds() totalSeconds := s.totalTime totalCount := s.totalCount errorCount := s.errorCount // Snapshot query maps (they are replaced as a whole on ResetStats). queryCacheTTL := s.queryCacheTTL queryScrapeTotalCount := s.queryScrapeTotalCount queryScrapeHitCount := s.queryScrapeHitCount queryScrapeErrorCount := s.queryScrapeErrorCount queryScrapePredicateSkipCount := s.queryScrapePredicateSkipCount queryScrapeMetricCount := s.queryScrapeMetricCount queryScrapeDuration := s.queryScrapeDuration s.lock.RUnlock() ch <- prometheus.MustNewConstMetric(e.serverScrapeDurationDesc, prometheus.GaugeValue, scrapeDur, datname) ch <- prometheus.MustNewConstMetric(e.serverScrapeTotalSecondsDesc, prometheus.GaugeValue, totalSeconds, datname) ch <- prometheus.MustNewConstMetric(e.serverScrapeTotalCountDesc, prometheus.GaugeValue, totalCount, datname) ch <- prometheus.MustNewConstMetric(e.serverScrapeErrorCountDesc, prometheus.GaugeValue, errorCount, datname) for queryName, v := range queryCacheTTL { ch <- prometheus.MustNewConstMetric(e.queryCacheTTLDesc, prometheus.GaugeValue, v, datname, queryName) } for queryName, v := range queryScrapeTotalCount { ch <- prometheus.MustNewConstMetric(e.queryScrapeTotalCountDesc, prometheus.GaugeValue, v, datname, queryName) } for queryName, v := range queryScrapeHitCount { ch <- prometheus.MustNewConstMetric(e.queryScrapeHitCountDesc, prometheus.GaugeValue, v, datname, queryName) } for queryName, v := range queryScrapeErrorCount { ch <- prometheus.MustNewConstMetric(e.queryScrapeErrorCountDesc, prometheus.GaugeValue, v, datname, queryName) } for queryName, v := range queryScrapePredicateSkipCount { ch <- prometheus.MustNewConstMetric(e.queryScrapePredicateSkipCountDesc, prometheus.GaugeValue, v, datname, queryName) } for queryName, v := range queryScrapeMetricCount { ch <- prometheus.MustNewConstMetric(e.queryScrapeMetricCountDesc, prometheus.GaugeValue, v, datname, queryName) } for queryName, v := range queryScrapeDuration { ch <- prometheus.MustNewConstMetric(e.queryScrapeDurationDesc, prometheus.GaugeValue, v, datname, queryName) } } } // Explain is a thin wrapper of server.Explain (plain text). func (e *Exporter) Explain() string { return e.server.Explain() } // Stat is just yet another wrapper of server.Stat func (e *Exporter) Stat() string { logDebugf("stats invoked") return e.server.Stat() } // Check will perform an immediate server health check func (e *Exporter) Check() { if err := e.probeAndUpdateHealthState(); err != nil { logErrorf("exporter check failure: %s", err.Error()) } else { logDebugf("exporter check ok") } } // Close will close all underlying servers func (e *Exporter) Close() { e.stopHealthLoop() if e.server != nil { if e.server.DB != nil { err := e.server.Close() if err != nil { logErrorf("fail closing server %s: %s", e.server.Name(), err.Error()) } } } // close peripheral servers (we may skip acquire lock here) for _, srv := range e.IterateServer() { if srv != nil { if srv.DB != nil { err := srv.Close() if err != nil { logErrorf("fail closing server %s: %s", srv.Name(), err.Error()) } } } } logInfof("pg exporter closed") } // setupInternalMetrics will init internal metrics func (e *Exporter) setupInternalMetrics() { if e.namespace == "" { if e.pgbouncerMode { e.namespace = "pgbouncer" } else { e.namespace = "pg" } } // major fact e.up = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: e.namespace, ConstLabels: e.constLabels, Name: "up", Help: "last scrape was able to connect to the server: 1 for yes, 0 for no", }) e.version = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: e.namespace, ConstLabels: e.constLabels, Name: "version", Help: "server version number", }) e.recovery = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: e.namespace, ConstLabels: e.constLabels, Name: "in_recovery", Help: "server is in recovery mode? 1 for yes 0 for no", }) // build info buildInfoLabels := prometheus.Labels{ "version": Version, "revision": Revision, "branch": Branch, "builddate": BuildDate, "goversion": GoVersion, "goos": GOOS, "goarch": GOARCH, } // Merge with user-provided constant labels for k, v := range e.constLabels { buildInfoLabels[k] = v } e.buildInfo = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: e.namespace, Name: "exporter_build_info", Help: "A metric with a constant '1' value labeled with version, revision, branch, goversion, builddate, goos, and goarch from which pg_exporter was built.", ConstLabels: buildInfoLabels, }) // Set the build info value e.buildInfo.Set(1) // exporter level metrics e.exporterUp = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: e.namespace, ConstLabels: e.constLabels, Subsystem: "exporter", Name: "up", Help: "always be 1 if your could retrieve metrics", }) e.exporterUptime = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: e.namespace, ConstLabels: e.constLabels, Subsystem: "exporter", Name: "uptime", Help: "seconds since exporter primary server inited", }) e.scrapeTotalCount = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: e.namespace, ConstLabels: e.constLabels, Subsystem: "exporter", Name: "scrape_total_count", Help: "times exporter was scraped for metrics", }) e.scrapeErrorCount = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: e.namespace, ConstLabels: e.constLabels, Subsystem: "exporter", Name: "scrape_error_count", Help: "times exporter was scraped for metrics and failed", }) e.scrapeDuration = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: e.namespace, ConstLabels: e.constLabels, Subsystem: "exporter", Name: "scrape_duration", Help: "seconds exporter spending on scraping", }) e.lastScrapeTime = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: e.namespace, ConstLabels: e.constLabels, Subsystem: "exporter", Name: "last_scrape_time", Help: "last scrape timestamp", }) // Dynamic per-server/per-query series. // These are described via *prometheus.Desc and emitted as const metrics on each scrape. e.serverScrapeDurationDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_server", "scrape_duration"), "seconds exporter server spending on scraping last scrape", []string{"datname"}, e.constLabels, ) e.serverScrapeTotalSecondsDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_server", "scrape_total_seconds"), "cumulative total seconds exporter server spending on scraping", []string{"datname"}, e.constLabels, ) e.serverScrapeTotalCountDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_server", "scrape_total_count"), "times exporter server was scraped for metrics", []string{"datname"}, e.constLabels, ) e.serverScrapeErrorCountDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_server", "scrape_error_count"), "cumulative times exporter server scrape failed (fatal scrape failures only)", []string{"datname"}, e.constLabels, ) e.queryCacheTTLDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_query", "cache_ttl"), "times to live of query cache", []string{"datname", "query"}, e.constLabels, ) e.queryScrapeTotalCountDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_query", "scrape_total_count"), "times exporter server was scraped for metrics", []string{"datname", "query"}, e.constLabels, ) e.queryScrapeErrorCountDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_query", "scrape_error_count"), "times the query failed", []string{"datname", "query"}, e.constLabels, ) e.queryScrapePredicateSkipCountDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_query", "scrape_predicate_skip_count"), "times the query was skipped due to a predicate returning false", []string{"datname", "query"}, e.constLabels, ) e.queryScrapeDurationDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_query", "scrape_duration"), "seconds query spending on scraping", []string{"datname", "query"}, e.constLabels, ) e.queryScrapeMetricCountDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_query", "scrape_metric_count"), "numbers of metrics been scraped from this query", []string{"datname", "query"}, e.constLabels, ) e.queryScrapeHitCountDesc = prometheus.NewDesc( prometheus.BuildFQName(e.namespace, "exporter_query", "scrape_hit_count"), "numbers been scraped from this query", []string{"datname", "query"}, e.constLabels, ) e.exporterUp.Set(1) // always be true e.healthStatus.Store(healthStatusUnknown) } func (e *Exporter) collectInternalMetrics(ch chan<- prometheus.Metric) { ch <- e.up ch <- e.version ch <- e.recovery ch <- e.buildInfo ch <- e.exporterUp ch <- e.exporterUptime ch <- e.lastScrapeTime ch <- e.scrapeTotalCount ch <- e.scrapeErrorCount ch <- e.scrapeDuration } /* ================ Exporter Creation ================ */ // NewExporter construct a PG Exporter instance for given dsn func NewExporter(dsn string, opts ...ExporterOpt) (e *Exporter, err error) { return newExporterWithServerFactory(dsn, NewServer, opts...) } func newExporterWithServerFactory(dsn string, serverFactory func(string, ...ServerOpt) *Server, opts ...ExporterOpt) (e *Exporter, err error) { e = &Exporter{dsn: dsn} e.servers = make(map[string]*Server) for _, opt := range opts { opt(e) } if len(e.configPath) > 0 && e.configReader != nil { return nil, errors.New("exporter configPath and configReader options are mutually exclusive") } if len(e.configPath) > 0 { if e.queries, err = LoadConfig(e.configPath); err != nil { return nil, fmt.Errorf("fail loading config file %s: %w", e.configPath, err) } } if e.configReader != nil { b, rerr := io.ReadAll(e.configReader) if rerr != nil { return nil, fmt.Errorf("fail reading config file: %w", rerr) } if e.queries, err = ParseConfig(b); err != nil { return nil, fmt.Errorf("fail parsing config file: %w", err) } if err := FinalizeQueries(e.queries, ""); err != nil { return nil, fmt.Errorf("fail finalizing config: %w", err) } } if err := validateConstLabelConflicts(e.constLabels, e.queries, e.disableIntro); err != nil { return nil, fmt.Errorf("invalid constant labels: %w", err) } logDebugf("exporter init with %d queries", len(e.queries)) // note here the server is still not connected. it will trigger connecting when being scraped e.server = serverFactory( dsn, WithQueries(e.queries), WithConstLabel(e.constLabels), WithCachePolicy(e.disableCache), WithServerTags(e.tags), WithServerConnectTimeout(e.connectTimeout), ) // register db change callback if e.autoDiscovery { logInfof("auto discovery is enabled, excludeDatabase=%v, includeDatabase=%v", e.excludeDatabase, e.includeDatabase) e.server.onDatabaseChange = e.OnDatabaseChange } logDebugf("check primary server connectivity") // Best-effort check: we don't block the exporter startup if the target is down. // The actual scrape path will reconnect and re-plan when the target comes back. if err = e.server.Check(); err != nil { if e.failFast { if e.server.DB != nil { if closeErr := e.server.Close(); closeErr != nil { logErrorf("fail closing primary server after connectivity check failure: %s", closeErr.Error()) } } return nil, fmt.Errorf("fail connecting to primary server: %w", err) } logErrorf("fail connecting to primary server: %s (startup will continue)", err.Error()) // NewExporter has named return values; make sure we don't propagate the // precheck error when failFast is disabled. err = nil } e.pgbouncerMode = e.server.PgbouncerMode e.setupInternalMetrics() e.updateHealthStateFromServer() // Always start the health loop so probes can recover once the target becomes reachable. e.startHealthLoop() return } // OnDatabaseChange will spawn new Server when new database is created // and destroy Server if corresponding database is dropped func (e *Exporter) OnDatabaseChange(change map[string]bool) { for dbname, add := range change { verb := "del" if add { verb = "add" } if dbname == e.server.Database { continue // skip primary database change } if _, found := e.excludeDatabase[dbname]; found { logInfof("skip database change: %v %v according to in excluded database list", verb, dbname) continue // skip exclude databases changes } if len(e.includeDatabase) > 0 { if _, found := e.includeDatabase[dbname]; !found { logInfof("skip database change: %v %v according to not in include database list", verb, dbname) continue // skip non-include databases changes } } if add { // spawn new server e.CreateServer(dbname) } else { // close old server e.RemoveServer(dbname) } } } // CreateServer will spawn new database server from a database name combined with existing dsn string // This happens when a database is newly created func (e *Exporter) CreateServer(dbname string) { newDSN := ReplaceDatname(e.dsn, dbname) logInfof("spawn new server for database %s : %s", dbname, ShadowPGURL(newDSN)) newServer := NewServer( newDSN, WithQueries(e.queries), WithConstLabel(e.constLabels), WithCachePolicy(e.disableCache), WithServerTags(e.tags), WithServerConnectTimeout(e.connectTimeout), ) newServer.Forked = true // important! e.sLock.Lock() e.servers[dbname] = newServer logInfof("database %s is installed due to auto-discovery", dbname) defer e.sLock.Unlock() } // RemoveServer will destroy Server from Exporter according to database name // This happens when a database is dropped func (e *Exporter) RemoveServer(dbname string) { e.sLock.Lock() srv, ok := e.servers[dbname] if ok { delete(e.servers, dbname) } e.sLock.Unlock() if ok && srv != nil { if srv.DB != nil { // Close asynchronously to avoid blocking the scrape path. go func(dbname string, srv *Server) { if err := srv.Close(); err != nil { logErrorf("fail closing removed database server %s: %s", dbname, err.Error()) } }(dbname, srv) } } logWarnf("database %s is removed due to auto-discovery", dbname) } // IterateServer will get snapshot of extra servers func (e *Exporter) IterateServer() (res []*Server) { e.sLock.RLock() defer e.sLock.RUnlock() if len(e.servers) == 0 { return nil } res = make([]*Server, 0, len(e.servers)) for _, srv := range e.servers { res = append(res, srv) } return } // ExporterOpt configures Exporter type ExporterOpt func(*Exporter) // WithConfig add config path to Exporter func WithConfig(configPath string) ExporterOpt { return func(e *Exporter) { e.configPath = configPath } } // WithConfigReader uses a the provided reader to load a configuration for the Exporter func WithConfigReader(reader io.Reader) ExporterOpt { return func(e *Exporter) { e.configReader = reader } } // WithConstLabels add const label to exporter. 0 length label returns nil func WithConstLabels(s string) ExporterOpt { return func(e *Exporter) { e.constLabels = parseConstLabels(s) } } // WithCacheDisabled set cache param to exporter func WithCacheDisabled(disableCache bool) ExporterOpt { return func(e *Exporter) { e.disableCache = disableCache } } // WithIntroDisabled will pass introspection option to server func WithIntroDisabled(disableIntro bool) ExporterOpt { return func(s *Exporter) { s.disableIntro = disableIntro } } // WithFailFast marks exporter fail instead of waiting during start-up func WithFailFast(failFast bool) ExporterOpt { return func(e *Exporter) { e.failFast = failFast } } // WithNamespace will specify metric namespace, by default is pg or pgbouncer func WithNamespace(namespace string) ExporterOpt { return func(e *Exporter) { e.namespace = namespace } } // WithTags will register given tags to Exporter and all belonged servers func WithTags(tags string) ExporterOpt { return func(e *Exporter) { e.tags = parseCSV(tags) } } // WithAutoDiscovery configures exporter with excluded database func WithAutoDiscovery(flag bool) ExporterOpt { return func(e *Exporter) { e.autoDiscovery = flag } } // WithExcludeDatabase configures exporter with excluded database func WithExcludeDatabase(excludeStr string) ExporterOpt { return func(e *Exporter) { exclMap := make(map[string]bool) exclList := parseCSV(excludeStr) for _, item := range exclList { exclMap[item] = true } e.excludeDatabase = exclMap } } // WithIncludeDatabase configures exporter with included database func WithIncludeDatabase(includeStr string) ExporterOpt { return func(e *Exporter) { inclMap := make(map[string]bool) inclList := parseCSV(includeStr) for _, item := range inclList { inclMap[item] = true } e.includeDatabase = inclMap } } // WithConnectTimeout will specify timeout for conn pre-check. // It's useful to increase this value when monitoring a remote instance (cross DC, cross AZ) func WithConnectTimeout(timeout int) ExporterOpt { return func(e *Exporter) { e.connectTimeout = timeout } } /* ================ Exporter RESTAPI ================ */ func currentExporter() *Exporter { if target := currentExporterPt.Load(); target != nil { return target } ReloadLock.RLock() defer ReloadLock.RUnlock() return PgExporter } // ExplainFunc expose explain document func (e *Exporter) ExplainFunc(w http.ResponseWriter, r *http.Request) { // The explain output is plain text. Serving it as text/plain avoids // browsers interpreting config content as HTML. w.Header().Set("Content-Type", "text/plain; charset=utf-8") target := currentExporter() if target == nil { w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte("exporter unavailable")) return } _, _ = w.Write([]byte(target.Explain())) } // StatFunc exposes plain text runtime statistics. func (e *Exporter) StatFunc(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=UTF-8") target := currentExporter() if target == nil { w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte("exporter unavailable")) return } _, _ = w.Write([]byte(target.Stat())) } // UpCheckFunc tells whether target instance is alive, 200 up 503 down func (e *Exporter) UpCheckFunc(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") target := currentExporter() if target == nil { w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte("unknown")) return } // Note: /up reports the latest state from the background health loop. // It does not actively probe the target on each HTTP request. status := target.Status() if target.Up() { w.WriteHeader(200) _, _ = w.Write([]byte(status)) } else { w.WriteHeader(503) _, _ = w.Write([]byte(status)) } } // PrimaryCheckFunc tells whether target instance is a primary, 200 yes 404 no 503 unknown func (e *Exporter) PrimaryCheckFunc(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") target := currentExporter() if target == nil { w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte("unknown")) return } status := target.Status() if target.Up() { if target.Recovery() { w.WriteHeader(404) _, _ = w.Write([]byte(status)) } else { w.WriteHeader(200) _, _ = w.Write([]byte(status)) } } else { w.WriteHeader(503) _, _ = w.Write([]byte(status)) } } // ReplicaCheckFunc tells whether target instance is a replica, 200 yes 404 no 503 unknown func (e *Exporter) ReplicaCheckFunc(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") target := currentExporter() if target == nil { w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte("unknown")) return } status := target.Status() if target.Up() { if target.Recovery() { w.WriteHeader(200) _, _ = w.Write([]byte(status)) } else { w.WriteHeader(404) _, _ = w.Write([]byte(status)) } } else { w.WriteHeader(503) _, _ = w.Write([]byte(status)) } } // VersionFunc responding current pg_exporter version func VersionFunc(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") payload := fmt.Sprintf("pg_exporter version %s\nrevision: %s\nbranch: %s\ngo version: %s\nbuild date: %s\ngoos: %s\ngoarch: %s", Version, Revision, Branch, GoVersion, BuildDate, GOOS, GOARCH) _, _ = w.Write([]byte(payload)) } // TitleFunc responding a description message func TitleFunc(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=UTF-8") _, _ = w.Write([]byte(`PG Exporter

PG Exporter

Metrics

`)) } // ReloadFunc handles reload request func ReloadFunc(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") if r.Method != http.MethodPost && r.Method != http.MethodGet { w.Header().Set("Allow", "GET, POST") w.WriteHeader(http.StatusMethodNotAllowed) _, _ = w.Write([]byte("method not allowed")) return } if err := Reload(); err != nil { w.WriteHeader(500) _, _ = w.Write([]byte(fmt.Sprintf("fail to reload: %s", err.Error()))) } else { _, _ = w.Write([]byte(`server reloaded`)) } } pg_exporter-1.4.1/exporter/exporter_handlers_opts_test.go000066400000000000000000000065731523237665500241370ustar00rootroot00000000000000package exporter import ( "net/http" "net/http/httptest" "strings" "sync/atomic" "testing" ) func TestExporterOptionHelpers(t *testing.T) { e := &Exporter{} WithConfig("/tmp/c.yml")(e) WithConstLabels("k=v,env=prod")(e) WithCacheDisabled(true)(e) WithIntroDisabled(true)(e) WithFailFast(true)(e) WithNamespace("custom")(e) WithTags("a,b")(e) WithAutoDiscovery(true)(e) WithExcludeDatabase("template0,template1")(e) WithIncludeDatabase("app,metrics")(e) WithConnectTimeout(500)(e) if e.configPath != "/tmp/c.yml" { t.Fatalf("configPath = %s", e.configPath) } if e.constLabels["k"] != "v" || e.constLabels["env"] != "prod" { t.Fatalf("constLabels = %v", e.constLabels) } if !e.disableCache || !e.disableIntro || !e.failFast || !e.autoDiscovery { t.Fatalf("boolean options not applied: cache=%v intro=%v failFast=%v auto=%v", e.disableCache, e.disableIntro, e.failFast, e.autoDiscovery) } if e.namespace != "custom" { t.Fatalf("namespace = %s", e.namespace) } if len(e.tags) != 2 || e.tags[0] != "a" || e.tags[1] != "b" { t.Fatalf("tags = %#v", e.tags) } if !e.excludeDatabase["template0"] || !e.excludeDatabase["template1"] { t.Fatalf("excludeDatabase = %v", e.excludeDatabase) } if !e.includeDatabase["app"] || !e.includeDatabase["metrics"] { t.Fatalf("includeDatabase = %v", e.includeDatabase) } if e.connectTimeout != 500 { t.Fatalf("connectTimeout = %d", e.connectTimeout) } } func TestPublicHandlers(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) wTitle := httptest.NewRecorder() TitleFunc(wTitle, req) if wTitle.Code != http.StatusOK || !strings.Contains(wTitle.Body.String(), "PG Exporter") { t.Fatalf("TitleFunc unexpected response: code=%d body=%s", wTitle.Code, wTitle.Body.String()) } wVersion := httptest.NewRecorder() VersionFunc(wVersion, req) if wVersion.Code != http.StatusOK || !strings.Contains(wVersion.Body.String(), "pg_exporter version") { t.Fatalf("VersionFunc unexpected response: code=%d body=%s", wVersion.Code, wVersion.Body.String()) } } func TestExplainAndStatHandlersWhenExporterUnavailable(t *testing.T) { origin := PgExporter setCurrentExporter(nil) defer setCurrentExporter(origin) e := &Exporter{} req := httptest.NewRequest(http.MethodGet, "/explain", nil) wExplain := httptest.NewRecorder() e.ExplainFunc(wExplain, req) if wExplain.Code != http.StatusServiceUnavailable { t.Fatalf("ExplainFunc status = %d, want 503", wExplain.Code) } wStat := httptest.NewRecorder() e.StatFunc(wStat, req) if wStat.Code != http.StatusServiceUnavailable { t.Fatalf("StatFunc status = %d, want 503", wStat.Code) } } func TestHealthHandlersPassiveModeNoActiveProbe(t *testing.T) { var checkCount atomic.Int32 s := &Server{ Database: "postgres", Databases: map[string]bool{"postgres": true}, } s.beforeScrape = func(s *Server) error { checkCount.Add(1) s.UP = false s.Recovery = false return nil } e := &Exporter{server: s} e.updateHealthState(true, false) // cached primary/up state origin := PgExporter setCurrentExporter(e) defer setCurrentExporter(origin) req := httptest.NewRequest(http.MethodGet, "/up", nil) w := httptest.NewRecorder() e.UpCheckFunc(w, req) if w.Code != http.StatusOK { t.Fatalf("passive health check should use cached up status, got %d", w.Code) } if checkCount.Load() != 0 { t.Fatalf("passive health check should not probe DB, count=%d", checkCount.Load()) } } pg_exporter-1.4.1/exporter/global.go000066400000000000000000000017331523237665500175340ustar00rootroot00000000000000package exporter import ( "log/slog" "runtime" "sync" "sync/atomic" ) /* ================ Parameters ================ */ // Version is the fallback for plain go builds; make build overrides it via // ldflags with the Makefile VERSION. Keep both v-prefixed and in sync so every // build path reports the same string. var Version = "v1.4.1" // Build information. Populated at build-time. var ( Branch = "main" Revision = "HEAD" BuildDate = "20250421212100" // will be overwritten during release GoVersion = runtime.Version() GOOS = runtime.GOOS GOARCH = runtime.GOARCH ) var defaultPGURL = "postgresql:///?sslmode=disable" /* ================ Global Vars ================ */ // PgExporter is the global singleton of Exporter var ( PgExporter *Exporter currentExporterPt atomic.Pointer[Exporter] ReloadLock sync.RWMutex Logger = slog.Default() ) func setCurrentExporter(e *Exporter) { PgExporter = e currentExporterPt.Store(e) } pg_exporter-1.4.1/exporter/health_state_test.go000066400000000000000000000030441523237665500217750ustar00rootroot00000000000000package exporter import ( "errors" "fmt" "testing" "github.com/lib/pq" ) func TestIsPostgresStartupError(t *testing.T) { if !isPostgresStartupError(&pq.Error{Code: pq.ErrorCode(pgSQLStateCannotConnectNow)}) { t.Fatal("expected SQLSTATE 57P03 to be recognized as startup error") } wrapped := fmt.Errorf("wrapped: %w", &pq.Error{Code: pq.ErrorCode(pgSQLStateCannotConnectNow)}) if !isPostgresStartupError(wrapped) { t.Fatal("expected wrapped SQLSTATE 57P03 to be recognized as startup error") } if isPostgresStartupError(errors.New("plain error")) { t.Fatal("plain error should not be recognized as startup error") } if isPostgresStartupError(&pq.Error{Code: "08006"}) { t.Fatal("non-57P03 postgres error should not be recognized as startup error") } } func TestUpdateHealthStateWithStartup(t *testing.T) { e := &Exporter{} e.updateHealthStateWithStartup(false, false, true) if e.Up() { t.Fatal("startup state should not be considered up") } if e.Recovery() { t.Fatal("startup state should not expose recovery=true") } if got := e.Status(); got != "starting" { t.Fatalf("status = %s, want starting", got) } e.updateHealthStateWithStartup(false, false, false) if got := e.Status(); got != "down" { t.Fatalf("status = %s, want down", got) } e.updateHealthStateWithStartup(true, true, false) if got := e.Status(); got != "replica" { t.Fatalf("status = %s, want replica", got) } e.updateHealthStateWithStartup(true, false, false) if got := e.Status(); got != "primary" { t.Fatalf("status = %s, want primary", got) } } pg_exporter-1.4.1/exporter/histogram_acceptance_test.go000066400000000000000000000376361523237665500235110ustar00rootroot00000000000000package exporter import ( "context" "database/sql/driver" "errors" "fmt" "io" "os" "path/filepath" "reflect" "strings" "sync" "testing" "time" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) func TestHistogramAcceptanceMultipleColumnsAggregateIndependently(t *testing.T) { query := histogramTestQuery( "test_multi_hist", []string{"datname"}, map[string]*Column{ "query_time": {Name: "query_time", Usage: HISTOGRAM, Bucket: []float64{1, 10}}, "xid_age": {Name: "xid_age", Usage: HISTOGRAM, Bucket: []float64{100, 1000}}, }, []string{"query_time", "xid_age"}, ) collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{ columns: []string{"datname", "query_time", "xid_age"}, values: [][]driver.Value{ {"db1", float64(0.5), float64(50)}, {"db1", float64(5), float64(500)}, {"db1", nil, float64(5000)}, {"db2", float64(20), nil}, }, } }, nil) collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err != nil { t.Fatalf("execute two histogram columns: %v", err) } // query_time has two label groups and xid_age has one. Each two-bucket // histogram group emits two finite buckets, +Inf, count, and sum. if got := collector.ResultSize(); got != 15 { t.Fatalf("result size = %d, want 15", got) } samples, _ := gatherHistogramSamples(t, collector) db1 := map[string]string{"cluster": "c1", "datname": "db1"} requireHistogramSample(t, samples, "test_multi_hist_query_time_bucket", map[string]string{"cluster": "c1", "datname": "db1", "le": "1"}, 1) requireHistogramSample(t, samples, "test_multi_hist_query_time_bucket", map[string]string{"cluster": "c1", "datname": "db1", "le": "10"}, 2) requireHistogramSample(t, samples, "test_multi_hist_query_time_count", db1, 2) requireHistogramSample(t, samples, "test_multi_hist_query_time_sum", db1, 5.5) requireHistogramSample(t, samples, "test_multi_hist_xid_age_bucket", map[string]string{"cluster": "c1", "datname": "db1", "le": "100"}, 1) requireHistogramSample(t, samples, "test_multi_hist_xid_age_bucket", map[string]string{"cluster": "c1", "datname": "db1", "le": "1000"}, 2) requireHistogramSample(t, samples, "test_multi_hist_xid_age_count", db1, 3) requireHistogramSample(t, samples, "test_multi_hist_xid_age_sum", db1, 5550) requireHistogramSample(t, samples, "test_multi_hist_query_time_count", map[string]string{"cluster": "c1", "datname": "db2"}, 1) } func TestHistogramAcceptanceBelowFirstBucketAndAllNullTuple(t *testing.T) { query := histogramTestQuery( "test_absent_hist", []string{"datname"}, map[string]*Column{ "value": {Name: "value", Usage: HISTOGRAM, Bucket: []float64{0, 1}}, }, []string{"value"}, ) collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{ columns: []string{"datname", "value"}, values: [][]driver.Value{ {"below", float64(-5)}, {"all_null", nil}, {"all_null", nil}, }, } }, nil) collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err != nil { t.Fatalf("execute histogram query: %v", err) } if got := collector.ResultSize(); got != 5 { t.Fatalf("result size = %d, want only one two-bucket group (5 metrics)", got) } samples, _ := gatherHistogramSamples(t, collector) requireHistogramSample(t, samples, "test_absent_hist_value_bucket", map[string]string{"cluster": "c1", "datname": "below", "le": "0"}, 1) requireHistogramSample(t, samples, "test_absent_hist_value_bucket", map[string]string{"cluster": "c1", "datname": "below", "le": "1"}, 1) requireHistogramSample(t, samples, "test_absent_hist_value_count", map[string]string{"cluster": "c1", "datname": "below"}, 1) for key := range samples { if strings.Contains(key, "datname=all_null") { t.Fatalf("all-NULL label tuple unexpectedly emitted sample %s", key) } } } func TestHistogramAcceptanceScientificNotationLEIsStable(t *testing.T) { query := histogramTestQuery( "test_large_boundary", []string{"datname"}, map[string]*Column{ "age": {Name: "age", Usage: HISTOGRAM, Bucket: []float64{3000000}}, }, []string{"age"}, ) collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{ columns: []string{"datname", "age"}, values: [][]driver.Value{{"db", float64(1)}}, } }, nil) collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err != nil { t.Fatalf("execute histogram query: %v", err) } samples, _ := gatherHistogramSamples(t, collector) requireHistogramSample(t, samples, "test_large_boundary_age_bucket", map[string]string{ "cluster": "c1", "datname": "db", "le": "3e+06", }, 1) } func TestHistogramAcceptanceRenamedLabelOrderAndLELast(t *testing.T) { query := histogramTestQuery( "test_label_order", []string{"first_source", "second_source"}, map[string]*Column{ "duration": {Name: "duration", Usage: HISTOGRAM, Bucket: []float64{1}}, }, []string{"duration"}, ) query.Columns["first_source"].Rename = "zeta" query.Columns["second_source"].Rename = "alpha" collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{ columns: []string{"first_source", "second_source", "duration"}, values: [][]driver.Value{{"Z", "A", float64(0.5)}}, } }, nil) descriptors := collector.histogramDesc["duration"] if got := descriptors.bucket.String(); !strings.Contains(got, "variableLabels: {zeta,alpha,le}") { t.Fatalf("bucket descriptor label order = %s, want zeta,alpha,le", got) } for component, descriptor := range map[string]*prometheus.Desc{ "count": descriptors.count, "sum": descriptors.sum, } { if got := descriptor.String(); !strings.Contains(got, "variableLabels: {zeta,alpha}") { t.Fatalf("%s descriptor label order = %s, want zeta,alpha", component, got) } } collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err != nil { t.Fatalf("execute histogram query: %v", err) } samples, _ := gatherHistogramSamples(t, collector) requireHistogramSample(t, samples, "test_label_order_duration_bucket", map[string]string{ "cluster": "c1", "zeta": "Z", "alpha": "A", "le": "1", }, 1) } func TestHistogramAcceptanceFailedRefreshCachesEmptySnapshot(t *testing.T) { query := histogramTestQuery( "test_failed_ttl", []string{"datname"}, map[string]*Column{ "duration": {Name: "duration", Usage: HISTOGRAM, Bucket: []float64{1}}, }, []string{"duration"}, ) query.TTL = 60 queryCount := 0 collector := newHistogramTestCollector(t, query, func() driver.Rows { value := driver.Value("invalid") if queryCount > 1 { value = float64(0.5) } return &histogramTestRows{ columns: []string{"datname", "duration"}, values: [][]driver.Value{{"db", value}}, } }, &queryCount) firstScrape := time.Now() collector.Server.scrapeBegin = firstScrape first := make(chan prometheus.Metric, 4) collector.Collect(first) if queryCount != 1 || len(first) != 0 || collector.Error() == nil { t.Fatalf("failed refresh: queries=%d metrics=%d err=%v, want 1, 0, non-nil", queryCount, len(first), collector.Error()) } collector.Server.scrapeBegin = firstScrape.Add(30 * time.Second) second := make(chan prometheus.Metric, 4) collector.Collect(second) if queryCount != 1 { t.Fatalf("TTL cache retried failed execution: query count = %d, want 1", queryCount) } if len(second) != 0 || !collector.CacheHit() { t.Fatalf("cached failed snapshot: metrics=%d cacheHit=%v, want 0 and true", len(second), collector.CacheHit()) } if collector.Error() == nil { t.Fatal("cached failed snapshot lost the query error before TTL expiry") } collector.Server.scrapeBegin = firstScrape.Add(61 * time.Second) third := make(chan prometheus.Metric, 4) collector.Collect(third) if queryCount != 2 || len(third) != 4 || collector.CacheHit() || collector.Error() != nil { t.Fatalf("post-TTL recovery: queries=%d metrics=%d cacheHit=%v err=%v, want 2, 4, false, nil", queryCount, len(third), collector.CacheHit(), collector.Error()) } } func TestHistogramAcceptanceQueryStartErrorIsAtomic(t *testing.T) { tests := []struct { name string err error want string }{ {name: "driver error", err: errors.New("query start failed"), want: "query start failed"}, {name: "deadline", err: context.DeadlineExceeded, want: "timeout"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { query := histogramTestQuery( "test_query_error", []string{"datname"}, map[string]*Column{ "current": {Name: "current", Usage: GAUGE}, "duration": {Name: "duration", Usage: HISTOGRAM, Bucket: []float64{1}}, }, []string{"current", "duration"}, ) query.Timeout = 1 collector := newHistogramQueryErrorCollector(t, query, tt.err) collector.result = []prometheus.Metric{ prometheus.MustNewConstMetric(collector.descriptors["current"], prometheus.GaugeValue, 99, "db"), prometheus.MustNewConstMetric(collector.histogramDesc["duration"].count, prometheus.GaugeValue, 1, "db"), } collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err == nil || !strings.Contains(err.Error(), tt.want) { t.Fatalf("query start error = %v, want substring %q", err, tt.want) } if got := collector.ResultSize(); got != 0 { t.Fatalf("query start failure retained/published %d metrics", got) } }) } } type histogramAcceptanceErrorRows struct { columns []string values [][]driver.Value index int err error } func (r *histogramAcceptanceErrorRows) Columns() []string { return r.columns } func (r *histogramAcceptanceErrorRows) Close() error { return nil } func (r *histogramAcceptanceErrorRows) Next(dest []driver.Value) error { if r.index < len(r.values) { copy(dest, r.values[r.index]) r.index++ return nil } if r.err == nil { return io.EOF } return r.err } func TestHistogramAcceptanceRowsErrorIsAtomic(t *testing.T) { query := histogramTestQuery( "test_rows_error", []string{"datname"}, map[string]*Column{ "current": {Name: "current", Usage: GAUGE}, "duration": {Name: "duration", Usage: HISTOGRAM, Bucket: []float64{1}}, }, []string{"current", "duration"}, ) iterationErr := errors.New("driver rows failed") collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramAcceptanceErrorRows{ columns: []string{"datname", "current", "duration"}, values: [][]driver.Value{{"db", int64(1), float64(0.5)}}, err: iterationErr, } }, nil) collector.result = []prometheus.Metric{ prometheus.MustNewConstMetric(collector.descriptors["current"], prometheus.GaugeValue, 99, "db"), } collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err == nil || !strings.Contains(err.Error(), iterationErr.Error()) { t.Fatalf("rows.Err failure = %v, want wrapped %q", err, iterationErr) } if got := collector.ResultSize(); got != 0 { t.Fatalf("rows.Err published %d partial/stale metrics", got) } } func TestHistogramAcceptanceResultOrderIsDeterministic(t *testing.T) { query := histogramTestQuery( "test_order", []string{"left", "right"}, map[string]*Column{ "second": {Name: "second", Usage: HISTOGRAM, Bucket: []float64{10}}, "first": {Name: "first", Usage: HISTOGRAM, Bucket: []float64{1}}, }, []string{"second", "first"}, ) queryCount := 0 collector := newHistogramTestCollector(t, query, func() driver.Rows { rows := [][]driver.Value{ {"a", "bc", float64(5), float64(0.5)}, {"ab", "c", float64(20), float64(2)}, } if queryCount == 1 { rows[0], rows[1] = rows[1], rows[0] } return &histogramTestRows{ columns: []string{"left", "right", "second", "first"}, values: rows, } }, &queryCount) collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err != nil { t.Fatalf("first execute: %v", err) } first := histogramAcceptanceMetricSequence(t, collector.result) collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err != nil { t.Fatalf("second execute: %v", err) } second := histogramAcceptanceMetricSequence(t, collector.result) if !reflect.DeepEqual(first, second) { t.Fatalf("metric order changed across equivalent row sets:\nfirst: %v\nsecond: %v", first, second) } } func histogramAcceptanceMetricSequence(t *testing.T, metrics []prometheus.Metric) []string { t.Helper() result := make([]string, len(metrics)) for i, metric := range metrics { var dtoMetric dto.Metric if err := metric.Write(&dtoMetric); err != nil { t.Fatalf("write metric %d: %v", i, err) } labels := make([]string, len(dtoMetric.GetLabel())) for j, pair := range dtoMetric.GetLabel() { labels[j] = pair.GetName() + "=" + pair.GetValue() } result[i] = fmt.Sprintf("%s|%s|%g", metric.Desc(), strings.Join(labels, ","), dtoMetric.GetGauge().GetValue()) } return result } func TestHistogramAcceptanceConcurrentCollectIsRaceSafe(t *testing.T) { query := histogramTestQuery( "test_concurrent", []string{"datname"}, map[string]*Column{ "duration": {Name: "duration", Usage: HISTOGRAM, Bucket: []float64{1, 10}}, }, []string{"duration"}, ) collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{ columns: []string{"datname", "duration"}, values: [][]driver.Value{{"db", float64(0.5)}, {"db", float64(5)}}, } }, nil) collector.Server.DisableCache = true collector.Server.scrapeBegin = time.Now() const workers = 12 errorsCh := make(chan error, workers) var wg sync.WaitGroup for i := 0; i < workers; i++ { wg.Add(1) go func() { defer wg.Done() metrics := make(chan prometheus.Metric, 5) collector.Collect(metrics) if got := len(metrics); got != 5 { errorsCh <- fmt.Errorf("concurrent collect emitted %d metrics, want 5", got) } }() } wg.Wait() close(errorsCh) for err := range errorsCh { t.Error(err) } } func TestHistogramAcceptanceReloadDiscardsCachedCollector(t *testing.T) { originalExporter := PgExporter t.Cleanup(func() { setCurrentExporter(originalExporter) }) originalConfigPath := *configPath t.Cleanup(func() { *configPath = originalConfigPath }) oldQuery := histogramTestQuery( "reload_hist", []string{"datname"}, map[string]*Column{ "duration": {Name: "duration", Usage: HISTOGRAM, Bucket: []float64{1}}, }, []string{"duration"}, ) server := NewServer("postgresql://u:p@localhost:5432/postgres") oldCollector := NewCollector(oldQuery, server) oldCollector.result = []prometheus.Metric{ prometheus.MustNewConstMetric( oldCollector.histogramDesc["duration"].count, prometheus.GaugeValue, 1, "db", ), } server.queries = map[string]*Query{"reload_hist": oldQuery} server.Collectors = []*Collector{oldCollector} server.Planned = true server.ResetStats() exporter := &Exporter{ server: server, servers: map[string]*Server{}, queries: server.queries, } setCurrentExporter(exporter) dir := t.TempDir() path := filepath.Join(dir, "pg_exporter.yml") content := []byte(` reload_hist: query: SELECT 'db' AS datname, 0.5 AS duration metrics: - datname: {usage: LABEL} - duration: usage: HISTOGRAM bucket: [2, 4] `) if err := os.WriteFile(path, content, 0o644); err != nil { t.Fatalf("write reload config: %v", err) } *configPath = path if err := Reload(); err != nil { t.Fatalf("reload Histogram config: %v", err) } if server.Planned { t.Fatal("reload left server planned") } if server.Collectors != nil { t.Fatal("reload retained old Histogram collector and cached snapshot") } server.Plan() if len(server.Collectors) != 1 { t.Fatalf("replanned collectors = %d, want 1", len(server.Collectors)) } newCollector := server.Collectors[0] if newCollector == oldCollector { t.Fatal("reload reused the old Histogram collector") } if got := newCollector.ResultSize(); got != 0 { t.Fatalf("new Histogram collector inherited %d cached metrics", got) } if got := newCollector.Columns["duration"].Bucket; !reflect.DeepEqual(got, []float64{2, 4}) { t.Fatalf("reloaded buckets = %v, want [2 4]", got) } } pg_exporter-1.4.1/exporter/histogram_collector_test.go000066400000000000000000000350741523237665500234030ustar00rootroot00000000000000package exporter import ( "context" "database/sql" "database/sql/driver" "errors" "io" "math" "reflect" "sort" "strings" "testing" "time" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) type histogramTestConnector struct { rowsFactory func() driver.Rows queryCount *int queryErr error } func (c histogramTestConnector) Connect(context.Context) (driver.Conn, error) { return &histogramTestConn{rowsFactory: c.rowsFactory, queryCount: c.queryCount, queryErr: c.queryErr}, nil } func (c histogramTestConnector) Driver() driver.Driver { return histogramTestDriver{} } type histogramTestDriver struct{} func (histogramTestDriver) Open(string) (driver.Conn, error) { return nil, errors.New("histogram test driver must be opened through its connector") } type histogramTestConn struct { rowsFactory func() driver.Rows queryCount *int queryErr error } func (c *histogramTestConn) Prepare(string) (driver.Stmt, error) { return nil, errors.New("prepare not supported") } func (c *histogramTestConn) Close() error { return nil } func (c *histogramTestConn) Begin() (driver.Tx, error) { return nil, errors.New("transactions not supported") } func (c *histogramTestConn) QueryContext(context.Context, string, []driver.NamedValue) (driver.Rows, error) { if c.queryCount != nil { *c.queryCount++ } if c.queryErr != nil { return nil, c.queryErr } return c.rowsFactory(), nil } type histogramTestRows struct { columns []string values [][]driver.Value index int } func (r *histogramTestRows) Columns() []string { return r.columns } func (r *histogramTestRows) Close() error { return nil } func (r *histogramTestRows) Next(dest []driver.Value) error { if r.index >= len(r.values) { return io.EOF } copy(dest, r.values[r.index]) r.index++ return nil } func histogramTestQuery(name string, labels []string, metrics map[string]*Column, metricNames []string) *Query { columns := make(map[string]*Column, len(labels)+len(metrics)) columnNames := make([]string, 0, len(labels)+len(metrics)) for _, label := range labels { columns[label] = &Column{Name: label, Usage: LABEL} columnNames = append(columnNames, label) } for _, metricName := range metricNames { column := metrics[metricName] columns[metricName] = column columnNames = append(columnNames, metricName) if err := column.parseNumbers(); err != nil { panic(err) } } return &Query{ Name: name, Branch: name, SQL: "SELECT histogram test data", Columns: columns, ColumnNames: columnNames, LabelNames: append([]string(nil), labels...), MetricNames: append([]string(nil), metricNames...), } } func newHistogramTestCollector(t *testing.T, query *Query, factory func() driver.Rows, queryCount *int) *Collector { t.Helper() db := sql.OpenDB(histogramTestConnector{rowsFactory: factory, queryCount: queryCount}) t.Cleanup(func() { _ = db.Close() }) server := &Server{ DB: db, Database: "postgres", labels: prometheus.Labels{"cluster": "c1"}, } return NewCollector(query, server) } func newHistogramQueryErrorCollector(t *testing.T, query *Query, queryErr error) *Collector { t.Helper() db := sql.OpenDB(histogramTestConnector{queryErr: queryErr}) t.Cleanup(func() { _ = db.Close() }) server := &Server{ DB: db, Database: "postgres", labels: prometheus.Labels{"cluster": "c1"}, } return NewCollector(query, server) } func gatherHistogramSamples(t *testing.T, collector *Collector) (map[string]float64, map[string]*dto.MetricFamily) { t.Helper() collector.TTL = 3600 collector.Server.scrapeBegin = time.Now() collector.lastScrape = collector.Server.scrapeBegin registry := prometheus.NewRegistry() if err := registry.Register(collector); err != nil { t.Fatalf("register collector: %v", err) } families, err := registry.Gather() if err != nil { t.Fatalf("gather collector: %v", err) } samples := make(map[string]float64) familyMap := make(map[string]*dto.MetricFamily, len(families)) for _, family := range families { familyMap[family.GetName()] = family if family.GetType() != dto.MetricType_GAUGE { t.Fatalf("family %s type = %s, want GAUGE", family.GetName(), family.GetType()) } for _, metric := range family.GetMetric() { labels := make(map[string]string, len(metric.GetLabel())) for _, pair := range metric.GetLabel() { labels[pair.GetName()] = pair.GetValue() } samples[histogramSampleKey(family.GetName(), labels)] = metric.GetGauge().GetValue() } } return samples, familyMap } func histogramSampleKey(name string, labels map[string]string) string { keys := make([]string, 0, len(labels)) for key := range labels { keys = append(keys, key) } sort.Strings(keys) var b strings.Builder b.WriteString(name) for _, key := range keys { b.WriteByte('|') b.WriteString(key) b.WriteByte('=') b.WriteString(labels[key]) } return b.String() } func requireHistogramSample(t *testing.T, samples map[string]float64, name string, labels map[string]string, want float64) { t.Helper() key := histogramSampleKey(name, labels) got, found := samples[key] if !found { t.Fatalf("missing sample %s; got %v", key, samples) } if math.Abs(got-want) > 1e-12 { t.Fatalf("sample %s = %v, want %v", key, got, want) } } func TestHistogramCollectorSnapshotAggregation(t *testing.T) { query := histogramTestQuery( "test_hist", []string{"left", "right"}, map[string]*Column{ "duration": { Name: "duration", Usage: HISTOGRAM, Rename: "latency_seconds", Bucket: []float64{0.001, 1, 10}, Scale: "2", Desc: "Current duration", }, }, []string{"duration"}, ) values := [][]driver.Value{ {"a", "bc", float64(0.0005)}, // scaled to exact 0.001 boundary {"a", "bc", float64(0.5)}, // scaled to exact 1 boundary {"a", "bc", float64(2)}, {"a", "bc", float64(20)}, {"a", "bc", nil}, // NULL without default is absent {"ab", "c", float64(0.25)}, // collision check versus ["a", "bc"] } collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{columns: []string{"left", "right", "duration"}, values: values} }, nil) collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err != nil { t.Fatalf("execute histogram query: %v", err) } // Three finite buckets + +Inf + count + sum for each of two groups. if got := collector.ResultSize(); got != 12 { t.Fatalf("result size = %d, want 12", got) } // Within every group, runtime output is finite buckets, +Inf, count, sum. for i, suffix := range []string{"_bucket", "_bucket", "_bucket", "_bucket", "_count", "_sum"} { if got := collector.result[i].Desc().String(); !strings.Contains(got, `fqName: "test_hist_latency_seconds`+suffix+`"`) { t.Fatalf("metric %d descriptor = %s, want suffix %s", i, got, suffix) } } samples, families := gatherHistogramSamples(t, collector) if _, found := families["test_hist_latency_seconds"]; found { t.Fatal("histogram collector emitted an unwanted bare base metric") } if got := families["test_hist_latency_seconds_bucket"].GetHelp(); got != "Current duration (cumulative bucket)" { t.Fatalf("bucket HELP = %q", got) } if got := families["test_hist_latency_seconds_count"].GetHelp(); got != "Current duration (observation count)" { t.Fatalf("count HELP = %q", got) } if got := families["test_hist_latency_seconds_sum"].GetHelp(); got != "Current duration (observation sum)" { t.Fatalf("sum HELP = %q", got) } base := map[string]string{"cluster": "c1", "left": "a", "right": "bc"} for le, want := range map[string]float64{"0.001": 1, "1": 2, "10": 3, "+Inf": 4} { labels := map[string]string{"cluster": base["cluster"], "left": base["left"], "right": base["right"], "le": le} requireHistogramSample(t, samples, "test_hist_latency_seconds_bucket", labels, want) } requireHistogramSample(t, samples, "test_hist_latency_seconds_count", base, 4) requireHistogramSample(t, samples, "test_hist_latency_seconds_sum", base, 45.001) second := map[string]string{"cluster": "c1", "left": "ab", "right": "c"} requireHistogramSample(t, samples, "test_hist_latency_seconds_count", second, 1) requireHistogramSample(t, samples, "test_hist_latency_seconds_sum", second, 0.5) } func TestHistogramCollectorDefaultThenScale(t *testing.T) { query := histogramTestQuery( "test_default", []string{"datname"}, map[string]*Column{ "duration": { Name: "duration", Usage: HISTOGRAM, Bucket: []float64{2, 6}, Scale: "2", Default: "3", }, }, []string{"duration"}, ) collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{ columns: []string{"datname", "duration"}, values: [][]driver.Value{{"db", nil}, {"db", "1"}}, } }, nil) collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err != nil { t.Fatalf("execute histogram query: %v", err) } samples, _ := gatherHistogramSamples(t, collector) base := map[string]string{"cluster": "c1", "datname": "db"} requireHistogramSample(t, samples, "test_default_duration_bucket", map[string]string{"cluster": "c1", "datname": "db", "le": "2"}, 1) requireHistogramSample(t, samples, "test_default_duration_bucket", map[string]string{"cluster": "c1", "datname": "db", "le": "6"}, 2) requireHistogramSample(t, samples, "test_default_duration_count", base, 2) requireHistogramSample(t, samples, "test_default_duration_sum", base, 8) } func TestHistogramCollectorInvalidObservationIsAtomic(t *testing.T) { for _, invalid := range []driver.Value{"bad", math.NaN(), math.Inf(1), math.Inf(-1)} { t.Run(strings.ReplaceAll(strings.TrimSpace(strings.ReplaceAll(toTestString(invalid), "/", "_")), " ", "_"), func(t *testing.T) { query := histogramTestQuery( "test_atomic", []string{"datname"}, map[string]*Column{ "current": {Name: "current", Usage: GAUGE}, "duration": {Name: "duration", Usage: HISTOGRAM, Bucket: []float64{1}}, }, []string{"current", "duration"}, ) collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{ columns: []string{"datname", "current", "duration"}, values: [][]driver.Value{{"db", int64(1), float64(0.5)}, {"db", int64(2), invalid}}, } }, nil) collector.result = []prometheus.Metric{ prometheus.MustNewConstMetric(collector.descriptors["current"], prometheus.GaugeValue, 99, "db"), } collector.scrapeBegin = time.Now() collector.execute() if collector.Error() == nil { t.Fatalf("invalid observation %v did not fail query", invalid) } if got := collector.ResultSize(); got != 0 { t.Fatalf("invalid observation published %d partial/stale metrics", got) } }) } } func toTestString(value driver.Value) string { if f, ok := value.(float64); ok { switch { case math.IsNaN(f): return "nan" case math.IsInf(f, 1): return "pos_inf" case math.IsInf(f, -1): return "neg_inf" } } return "invalid_string" } func TestHistogramCollectorMissingColumnIsError(t *testing.T) { query := histogramTestQuery( "test_missing", []string{"datname"}, map[string]*Column{"duration": {Name: "duration", Usage: HISTOGRAM, Bucket: []float64{1}}}, []string{"duration"}, ) collector := newHistogramTestCollector(t, query, func() driver.Rows { return &histogramTestRows{columns: []string{"datname"}, values: [][]driver.Value{{"db"}}} }, nil) collector.scrapeBegin = time.Now() collector.execute() if err := collector.Error(); err == nil || !strings.Contains(err.Error(), "missing histogram column") { t.Fatalf("missing histogram column error = %v", err) } if got := collector.ResultSize(); got != 0 { t.Fatalf("missing histogram column published %d metrics", got) } } func TestHistogramCollectorDoesNotAccumulateAcrossExecutionsOrCacheHits(t *testing.T) { query := histogramTestQuery( "test_refresh", []string{"datname"}, map[string]*Column{"duration": {Name: "duration", Usage: HISTOGRAM, Bucket: []float64{1}}}, []string{"duration"}, ) queryCount := 0 collector := newHistogramTestCollector(t, query, func() driver.Rows { var values [][]driver.Value switch queryCount { case 1: values = [][]driver.Value{{"db1", float64(0.5)}, {"db1", float64(2)}, {"db2", float64(0.5)}} case 2: values = [][]driver.Value{{"db1", float64(2)}} } return &histogramTestRows{ columns: []string{"datname", "duration"}, values: values, } }, &queryCount) collector.Server.DisableCache = true firstScrape := time.Now() collector.Server.scrapeBegin = firstScrape first := make(chan prometheus.Metric, 8) collector.Collect(first) if queryCount != 1 || len(first) != 8 { t.Fatalf("first collect queries=%d metrics=%d, want 1 and 8", queryCount, len(first)) } collector.Server.scrapeBegin = firstScrape.Add(time.Second) second := make(chan prometheus.Metric, 8) collector.Collect(second) if queryCount != 2 || len(second) != 4 { t.Fatalf("second collect queries=%d metrics=%d, want 2 and 4", queryCount, len(second)) } secondMetrics := make([]prometheus.Metric, 0, len(second)) for len(second) > 0 { secondMetrics = append(secondMetrics, <-second) } var firstFiniteBucket dto.Metric if err := secondMetrics[0].Write(&firstFiniteBucket); err != nil { t.Fatalf("write finite bucket: %v", err) } if got := firstFiniteBucket.GetGauge().GetValue(); got != 0 { t.Fatalf("refreshed finite bucket = %v, want 0 (no cross-execution accumulation)", got) } secondSequence := histogramAcceptanceMetricSequence(t, secondMetrics) for _, sample := range secondSequence { if strings.Contains(sample, "datname=db2") { t.Fatalf("refreshed snapshot retained disappeared label tuple: %s", sample) } } collector.Server.DisableCache = false collector.TTL = 3600 collector.Server.scrapeBegin = collector.lastScrape.Add(time.Second) cached := make(chan prometheus.Metric, 8) collector.Collect(cached) if queryCount != 2 || len(cached) != 4 { t.Fatalf("cache hit queries=%d metrics=%d, want 2 and 4", queryCount, len(cached)) } cachedMetrics := make([]prometheus.Metric, 0, len(cached)) for len(cached) > 0 { cachedMetrics = append(cachedMetrics, <-cached) } if cachedSequence := histogramAcceptanceMetricSequence(t, cachedMetrics); !reflect.DeepEqual(cachedSequence, secondSequence) { t.Fatalf("cache hit changed snapshot:\nreal: %v\ncached: %v", secondSequence, cachedSequence) } collector.Server.DisableCache = true collector.Server.scrapeBegin = collector.lastScrape.Add(2 * time.Second) empty := make(chan prometheus.Metric, 8) collector.Collect(empty) if queryCount != 3 || len(empty) != 0 || collector.ResultSize() != 0 { t.Fatalf("empty refresh queries=%d metrics=%d result=%d, want 3, 0, 0", queryCount, len(empty), collector.ResultSize()) } } pg_exporter-1.4.1/exporter/histogram_config_test.go000066400000000000000000000155561523237665500226650ustar00rootroot00000000000000package exporter import ( "strings" "testing" "github.com/prometheus/client_golang/prometheus" ) func TestParseConfigHistogramOnlyQuery(t *testing.T) { config := ` activity_hist: name: pg_activity_hist query: SELECT 'app' AS datname, 0.5 AS duration metrics: - datname: usage: label rename: database - duration: usage: histogram rename: query_duration_seconds bucket: [0.1, 1, 10] scale: "2" default: "0.25" description: Current query duration snapshot ` queries, err := ParseConfig([]byte(config)) if err != nil { t.Fatalf("ParseConfig returned error: %v", err) } q := queries["activity_hist"] if q == nil { t.Fatal("activity_hist query not found") } c := q.Columns["duration"] if c == nil { t.Fatal("duration column not found") } if c.Usage != HISTOGRAM { t.Fatalf("usage = %q, want %q", c.Usage, HISTOGRAM) } if !ColumnUsage[HISTOGRAM] { t.Fatal("HISTOGRAM should be classified as a metric column") } if len(q.MetricNames) != 1 || q.MetricNames[0] != "duration" { t.Fatalf("MetricNames = %#v, want [duration]", q.MetricNames) } if !q.HasHistogram() { t.Fatal("HasHistogram returned false for Histogram-only query") } if len(c.Bucket) != 3 || c.Bucket[0] != 0.1 || c.Bucket[2] != 10 { t.Fatalf("Bucket = %#v, want [0.1 1 10]", c.Bucket) } if !c.hasScale || c.scaleFactor != 2 { t.Fatalf("parsed scale = (%v, %v), want (true, 2)", c.hasScale, c.scaleFactor) } if !c.hasDefault || c.defaultValue != 0.25 { t.Fatalf("parsed default = (%v, %v), want (true, 0.25)", c.hasDefault, c.defaultValue) } metrics := q.MetricList() if len(metrics) != 1 { t.Fatalf("MetricList len = %d, want 1 logical Histogram", len(metrics)) } if got := metrics[0].Name; got != "pg_activity_hist_query_duration_seconds{database}" { t.Fatalf("logical Histogram metric name = %q", got) } explain := q.Explain() if !strings.Contains(explain, "Bucket [0.1 1 10]") { t.Fatalf("Explain does not show Histogram buckets:\n%s", explain) } marshaled := q.MarshalYAML() if !strings.Contains(marshaled, "bucket:") || !strings.Contains(marshaled, "- 0.1") { t.Fatalf("MarshalYAML does not preserve Histogram buckets:\n%s", marshaled) } } func TestParseConfigRejectsInvalidHistogramBuckets(t *testing.T) { tests := []struct { name string bucket string want string }{ {name: "missing", bucket: "", want: "at least one bucket"}, {name: "empty", bucket: "bucket: []", want: "at least one bucket"}, {name: "unsorted", bucket: "bucket: [1, 0.1]", want: "strictly increasing"}, {name: "duplicate", bucket: "bucket: [0.1, 0.1]", want: "strictly increasing"}, {name: "nan", bucket: "bucket: [.nan]", want: "must be finite"}, {name: "positive infinity", bucket: "bucket: [.inf]", want: "must be finite"}, {name: "negative infinity", bucket: "bucket: [-.inf]", want: "must be finite"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := ` bad_hist: query: SELECT 1 AS value metrics: - value: usage: HISTOGRAM ` + indentHistogramTestYAML(tt.bucket, 8) _, err := ParseConfig([]byte(config)) if err == nil { t.Fatal("ParseConfig should reject invalid Histogram buckets") } if !strings.Contains(err.Error(), tt.want) { t.Fatalf("error %q does not contain %q", err, tt.want) } }) } } func TestParseConfigRejectsHistogramLabelLEAfterRename(t *testing.T) { tests := []struct { name string label string }{ {name: "direct", label: "le"}, {name: "renamed", label: "upper_bound"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rename := "" if tt.name == "renamed" { rename = " rename: le\n" } config := ` bad_hist: query: SELECT 'x' AS ` + tt.label + `, 1 AS value metrics: - ` + tt.label + `: usage: LABEL ` + rename + ` - value: usage: HISTOGRAM bucket: [1] ` _, err := ParseConfig([]byte(config)) if err == nil { t.Fatal("ParseConfig should reject le as a Histogram query label") } if !strings.Contains(err.Error(), `label "le"`) { t.Fatalf("unexpected error: %v", err) } }) } // le is not globally reserved for scalar-only queries. scalar := ` scalar: query: SELECT 'x' AS le, 1 AS value metrics: - le: {usage: LABEL} - value: {usage: GAUGE} ` if _, err := ParseConfig([]byte(scalar)); err != nil { t.Fatalf("scalar-only query should allow label le: %v", err) } } func TestParseConfigRejectsHistogramDerivedNameCollisions(t *testing.T) { tests := []struct { name string otherUsage string otherRename string }{ {name: "bucket scalar", otherUsage: GAUGE, otherRename: "latency_bucket"}, {name: "count scalar", otherUsage: COUNTER, otherRename: "latency_count"}, {name: "sum scalar", otherUsage: GAUGE, otherRename: "latency_sum"}, {name: "duplicate scalar base", otherUsage: GAUGE, otherRename: "latency"}, {name: "derived Histogram base", otherUsage: HISTOGRAM, otherRename: "latency_bucket"}, {name: "duplicate logical base", otherUsage: HISTOGRAM, otherRename: "latency"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { otherBucket := "" if tt.otherUsage == HISTOGRAM { otherBucket = "\n bucket: [1, 2]" } config := ` collision: name: pg_collision query: SELECT 1 AS duration, 2 AS other metrics: - duration: usage: HISTOGRAM rename: latency bucket: [0.1, 1] - other: usage: ` + tt.otherUsage + ` rename: ` + tt.otherRename + otherBucket + ` ` _, err := ParseConfig([]byte(config)) if err == nil { t.Fatal("ParseConfig should reject Histogram family collision") } if !strings.Contains(err.Error(), "conflicts") { t.Fatalf("unexpected error: %v", err) } }) } } func TestValidateConstLabelLEOnlyConflictsWithHistogram(t *testing.T) { histogramConfig := ` hist: query: SELECT 1 AS value metrics: - value: usage: HISTOGRAM bucket: [1] ` histograms, err := ParseConfig([]byte(histogramConfig)) if err != nil { t.Fatalf("ParseConfig Histogram failed: %v", err) } labels := prometheus.Labels{"le": "site"} if err := validateConstLabelConflicts(labels, histograms, true); err == nil { t.Fatal("expected const label le to conflict with Histogram query") } else if !strings.Contains(err.Error(), "generated Histogram bucket label") { t.Fatalf("unexpected error: %v", err) } scalarConfig := ` scalar: query: SELECT 1 AS value metrics: - value: {usage: GAUGE} ` scalars, err := ParseConfig([]byte(scalarConfig)) if err != nil { t.Fatalf("ParseConfig scalar failed: %v", err) } if err := validateConstLabelConflicts(labels, scalars, true); err != nil { t.Fatalf("const label le should be allowed without Histogram queries: %v", err) } } func indentHistogramTestYAML(value string, spaces int) string { if value == "" { return "" } return strings.Repeat(" ", spaces) + value + "\n" } pg_exporter-1.4.1/exporter/main.go000066400000000000000000000167121523237665500172230ustar00rootroot00000000000000package exporter import ( "fmt" "net/http" "net/url" "os" "os/signal" pathpkg "path" "sort" "strings" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/exporter-toolkit/web" ) // validateTelemetryPath checks that path can be registered alongside all of // pg_exporter's fixed endpoints. registerHTTPRoutes is deliberately reused so // validation cannot drift from the routes used by the real server. func validateTelemetryPath(path string) (err error) { if path == "" { return fmt.Errorf("web.telemetry-path must not be empty") } if path[0] != '/' { return fmt.Errorf("web.telemetry-path %q must start with '/'", path) } uri, parseErr := url.ParseRequestURI(path) if parseErr != nil || strings.ContainsAny(path, "?#") || uri.RawQuery != "" || uri.Fragment != "" { return fmt.Errorf("web.telemetry-path %q must be a valid URL path without query or fragment", path) } if strings.ContainsAny(path, "{}") { return fmt.Errorf("web.telemetry-path %q must be a literal URL path without ServeMux wildcards", path) } // ServeMux path-cleans incoming requests, so a non-canonical pattern like // "//metrics" or "/a/../metrics" registers fine but can never be matched if cleaned := pathpkg.Clean(path); path != cleaned && path != cleaned+"/" { return fmt.Errorf("web.telemetry-path %q must be a canonical URL path (did you mean %q?)", path, cleaned) } defer func() { if recovered := recover(); recovered != nil { err = fmt.Errorf("invalid or conflicting web.telemetry-path %q: %v", path, recovered) } }() registerHTTPRoutes(http.NewServeMux(), &Exporter{}, path, http.NotFoundHandler()) return nil } func registerHTTPRoutes(mux *http.ServeMux, e *Exporter, telemetryPath string, telemetryHandler http.Handler) { mux.HandleFunc("/", TitleFunc) mux.HandleFunc("/version", VersionFunc) mux.HandleFunc("/reload", ReloadFunc) mux.HandleFunc("/stat", e.StatFunc) mux.HandleFunc("/explain", e.ExplainFunc) for _, path := range []string{"/up", "/read", "/health", "/liveness", "/readiness"} { mux.HandleFunc(path, e.UpCheckFunc) } for _, path := range []string{"/primary", "/leader", "/master", "/read-write", "/rw"} { mux.HandleFunc(path, e.PrimaryCheckFunc) } for _, path := range []string{"/replica", "/standby", "/slave", "/read-only", "/ro"} { mux.HandleFunc(path, e.ReplicaCheckFunc) } mux.Handle(telemetryPath, telemetryHandler) } // clearLibPQEnvironment removes ambient PostgreSQL settings that pg_exporter // must not pass to lib/pq. In particular, lib/pq supports PGSERVICE and // PGSERVICEFILE, but service-file values can override the explicit connection // URL selected by pg_exporter. Ignoring them keeps the advertised URL and the // actual connection target deterministic. func clearLibPQEnvironment() { variables := []struct { name string reason string }{ {"PGSYSCONFDIR", "kept isolated for compatibility with older lib/pq"}, {"PGLOCALEDIR", "kept isolated for compatibility with older lib/pq"}, {"PGREALM", "rejected by lib/pq"}, {"PGSERVICEFILE", "may override the explicit pg_exporter URL"}, {"PGSERVICE", "may override the explicit pg_exporter URL"}, } for _, variable := range variables { if _, exists := os.LookupEnv(variable.name); !exists { continue } logWarnf("clearing environment variable %s (%s)", variable.name, variable.reason) if err := os.Unsetenv(variable.name); err != nil { logWarnf("failed to clear environment variable %s: %v", variable.name, err) } } } // DryRun will explain all query fetched from configs func DryRun() { configs, err := LoadConfig(*configPath) if err != nil { logErrorf("fail loading config %s, %v", *configPath, err) os.Exit(1) } var queries []*Query for _, query := range configs { queries = append(queries, query) } sort.Slice(queries, func(i, j int) bool { return queries[i].Priority < queries[j].Priority }) for _, query := range queries { fmt.Println(query.Explain()) } fmt.Println() os.Exit(0) } // Reload will launch a new pg exporter instance func Reload() error { ReloadLock.Lock() defer ReloadLock.Unlock() logDebugf("reload request received, reloading configuration") if *configPath == "" { return fmt.Errorf("no valid config path") } queries, err := LoadConfig(*configPath) if err != nil { return fmt.Errorf("fail loading config %s: %w", *configPath, err) } target := PgExporter if target == nil { return fmt.Errorf("exporter unavailable") } if err := validateConstLabelConflicts(target.constLabels, queries, target.disableIntro); err != nil { return fmt.Errorf("invalid configuration with current constant labels: %w", err) } // Block scrapes while we swap the query set and invalidate plans. target.lock.Lock() defer target.lock.Unlock() target.queries = queries // Update queries for primary + discovered servers, and force re-plan on next scrape. servers := target.IterateServer() if target.server != nil { servers = append(servers, target.server) } for _, s := range servers { if s == nil { continue } s.lock.Lock() s.queries = queries s.Collectors = nil s.Planned = false s.ResetStats() s.lock.Unlock() } logInfof("server reloaded, %d queries applied", len(queries)) return nil } // Run pg_exporter func Run() { ParseArgs() clearLibPQEnvironment() // explain config only if *dryRun { DryRun() } if *configPath == "" { Logger.Error("no valid config path, exit") os.Exit(1) } if len(*webConfig.WebListenAddresses) == 0 { Logger.Error("invalid listen address", "addresses", *webConfig.WebListenAddresses) os.Exit(1) } listenAddr := (*webConfig.WebListenAddresses)[0] if err := validateTelemetryPath(*metricPath); err != nil { logErrorf("%v", err) os.Exit(1) } // Create exporter. It will connect on scrape and keep health probes running in background. var err error newExporter, err := NewExporter( *pgURL, WithConfig(*configPath), WithConstLabels(*constLabels), WithCacheDisabled(*disableCache), WithIntroDisabled(*disableIntro), WithFailFast(*failFast), WithNamespace(*exporterNamespace), WithAutoDiscovery(*autoDiscovery), WithExcludeDatabase(*excludeDatabase), WithIncludeDatabase(*includeDatabase), WithTags(*serverTags), WithConnectTimeout(*connectTimeout), ) if err != nil { logErrorf("fail creating pg_exporter: %s", err.Error()) os.Exit(2) } setCurrentExporter(newExporter) // trigger a manual planning before explain if *explainOnly { PgExporter.server.Plan() fmt.Println(PgExporter.Explain()) os.Exit(0) } prometheus.MustRegister(PgExporter) defer PgExporter.Close() // reload conf when receiving configured reload signals (SIGHUP, and SIGUSR1 on non-Windows) sigs := make(chan os.Signal, 1) signal.Notify(sigs, reloadSignals...) go func() { for sig := range sigs { logInfof("%v received, reloading", sig) if err := Reload(); err != nil { logErrorf("reload failed: %s", err.Error()) } } }() /* ================ REST API ================ */ mux := http.NewServeMux() registerHTTPRoutes(mux, PgExporter, *metricPath, promhttp.Handler()) logInfof("pg_exporter for %s start, listen on %s%s", ShadowPGURL(*pgURL), listenAddr, *metricPath) srv := &http.Server{ Handler: mux, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 2 * time.Minute, } if err := web.ListenAndServe(srv, webConfig, Logger); err != nil { logFatalf("http server failed: %s", err.Error()) } } pg_exporter-1.4.1/exporter/main_test.go000066400000000000000000000040341523237665500202540ustar00rootroot00000000000000package exporter import ( "os" "strings" "testing" ) func TestClearLibPQEnvironment(t *testing.T) { cleared := []string{ "PGSYSCONFDIR", "PGLOCALEDIR", "PGREALM", "PGSERVICEFILE", "PGSERVICE", } for _, name := range cleared { t.Setenv(name, "test-value") } t.Setenv("PGHOST", "preserved-host") clearLibPQEnvironment() for _, name := range cleared { if value, exists := os.LookupEnv(name); exists { t.Errorf("%s remains set to %q", name, value) } } if value := os.Getenv("PGHOST"); value != "preserved-host" { t.Errorf("PGHOST = %q, want %q", value, "preserved-host") } } func TestValidateTelemetryPath(t *testing.T) { tests := []struct { name string path string wantErr string }{ {name: "default", path: "/metrics"}, {name: "empty", path: "", wantErr: "must not be empty"}, {name: "missing leading slash", path: "metrics", wantErr: "must start with '/'"}, {name: "reserved endpoint", path: "/up", wantErr: "invalid or conflicting"}, {name: "escaped reserved endpoint", path: "/%75p", wantErr: "invalid or conflicting"}, {name: "root endpoint", path: "/", wantErr: "invalid or conflicting"}, {name: "wildcard", path: "/{x...}", wantErr: "literal URL path"}, {name: "end wildcard", path: "/{$}", wantErr: "literal URL path"}, {name: "nested wildcard", path: "/metrics/{id}", wantErr: "literal URL path"}, {name: "malformed escape", path: "/metrics/%zz", wantErr: "valid URL path"}, {name: "query is not a path", path: "/metrics?format=text", wantErr: "valid URL path"}, {name: "fragment is not a path", path: "/metrics#internal", wantErr: "valid URL path"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateTelemetryPath(tt.path) if tt.wantErr == "" { if err != nil { t.Fatalf("validateTelemetryPath(%q) returned unexpected error: %v", tt.path, err) } return } if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Fatalf("validateTelemetryPath(%q) error = %v, want substring %q", tt.path, err, tt.wantErr) } }) } } pg_exporter-1.4.1/exporter/metrics_lifecycle_test.go000066400000000000000000000217311523237665500230200ustar00rootroot00000000000000package exporter import ( "context" "database/sql" "database/sql/driver" "errors" "sync/atomic" "testing" "time" "github.com/prometheus/client_golang/prometheus" ) type closeTrackingConnector struct { closed *atomic.Bool } func (c closeTrackingConnector) Connect(context.Context) (driver.Conn, error) { return &closeTrackingConn{closed: c.closed}, nil } func (c closeTrackingConnector) Driver() driver.Driver { return closeTrackingDriver{} } type closeTrackingDriver struct{} func (closeTrackingDriver) Open(string) (driver.Conn, error) { return nil, errors.New("close tracking driver must be opened through its connector") } type closeTrackingConn struct { closed *atomic.Bool } func (c *closeTrackingConn) Prepare(string) (driver.Stmt, error) { return nil, errors.New("prepare not supported") } func (c *closeTrackingConn) Close() error { c.closed.Store(true) return nil } func (c *closeTrackingConn) Begin() (driver.Tx, error) { return nil, errors.New("transactions not supported") } func (c *closeTrackingConn) Ping(context.Context) error { return nil } func readGauge(t *testing.T, gauge prometheus.Gauge) float64 { t.Helper() registry := prometheus.NewRegistry() registry.MustRegister(gauge) families, err := registry.Gather() if err != nil { t.Fatalf("gather gauge metric: %v", err) } if len(families) != 1 || len(families[0].GetMetric()) != 1 || families[0].GetMetric()[0].GetGauge() == nil { t.Fatalf("gathered metric is not a single gauge: %v", families) } return families[0].GetMetric()[0].GetGauge().GetValue() } func makeCachedCollectorForServer(s *Server, name string, val float64) *Collector { q := makeGaugeQuery(name, 1) c := NewCollector(q, s) c.TTL = 3600 c.lastScrape = time.Now() metric := prometheus.MustNewConstMetric(c.descriptors["value"], prometheus.GaugeValue, val, "db") c.result = []prometheus.Metric{metric} c.err = nil return c } func TestExporterCollectAndInternalMetrics(t *testing.T) { primary := NewServer("postgresql://u:p@localhost:5432/postgres") primary.beforeScrape = func(s *Server) error { s.UP = true s.Version = 160000 s.Recovery = false return nil } primary.Planned = true primary.Collectors = []*Collector{makeCachedCollectorForServer(primary, "q_primary", 1)} primary.ResetStats() extra := NewServer("postgresql://u:p@localhost:5432/otherdb") extra.Forked = true extra.beforeScrape = func(s *Server) error { s.UP = true s.Version = 160000 s.Recovery = false return nil } extra.Planned = true extra.Collectors = []*Collector{makeCachedCollectorForServer(extra, "q_extra", 2)} extra.ResetStats() e := &Exporter{ server: primary, servers: map[string]*Server{"otherdb": extra}, } e.setupInternalMetrics() ch := make(chan prometheus.Metric, 256) e.Collect(ch) if !e.Up() { t.Fatal("Exporter should be UP after successful collect") } if e.Status() != "primary" { t.Fatalf("Exporter status = %s, want primary", e.Status()) } } func TestExporterRecoveryMetricRetainsLastKnownRoleWhenTargetGoesDown(t *testing.T) { primary := NewServer("postgresql://u:p@localhost:5432/postgres") scrape := 0 primary.beforeScrape = func(s *Server) error { scrape++ switch scrape { case 1: s.UP = true s.Version = 160000 s.Recovery = true return nil case 2: return errors.New("database unavailable") default: s.UP = true s.Version = 160000 s.Recovery = false return nil } } primary.Planned = true primary.ResetStats() e := &Exporter{server: primary, servers: map[string]*Server{}} e.setupInternalMetrics() e.Collect(make(chan prometheus.Metric, 64)) if got := readGauge(t, e.recovery); got != 1 { t.Fatalf("recovery metric after replica scrape = %v, want 1", got) } e.Collect(make(chan prometheus.Metric, 64)) if !primary.Recovery { t.Fatal("test fixture should retain the server's last known recovery state") } if got := readGauge(t, e.recovery); got != 1 { t.Fatalf("recovery metric after failed scrape = %v, want last known value 1", got) } e.Collect(make(chan prometheus.Metric, 64)) if got := readGauge(t, e.recovery); got != 0 { t.Fatalf("recovery metric after reconnecting as primary = %v, want 0", got) } } func TestNewExporterFailFastClosesPrimaryServer(t *testing.T) { closed := &atomic.Bool{} db := sql.OpenDB(closeTrackingConnector{closed: closed}) t.Cleanup(func() { _ = db.Close() }) if err := db.PingContext(context.Background()); err != nil { t.Fatalf("open close-tracking database connection: %v", err) } checkErr := errors.New("connectivity check failed") server := NewServer("postgresql://u:p@localhost:5432/postgres") server.DB = db server.beforeScrape = func(*Server) error { return checkErr } serverFactory := func(string, ...ServerOpt) *Server { return server } exporter, err := newExporterWithServerFactory( "postgresql://u:p@localhost:5432/postgres", serverFactory, WithFailFast(true), ) if exporter != nil { t.Fatal("NewExporter should return a nil exporter when fail-fast check fails") } if !errors.Is(err, checkErr) { t.Fatalf("NewExporter error = %v, want wrapped connectivity error", err) } if !closed.Load() { t.Fatal("NewExporter did not close the primary server database after fail-fast check failure") } } func TestNewExporterNonFailFastKeepsPrimaryServerOpen(t *testing.T) { closed := &atomic.Bool{} db := sql.OpenDB(closeTrackingConnector{closed: closed}) t.Cleanup(func() { _ = db.Close() }) if err := db.PingContext(context.Background()); err != nil { t.Fatalf("open close-tracking database connection: %v", err) } checkErr := errors.New("connectivity check failed") server := NewServer("postgresql://u:p@localhost:5432/postgres") server.DB = db server.beforeScrape = func(*Server) error { return checkErr } serverFactory := func(string, ...ServerOpt) *Server { return server } exporter, err := newExporterWithServerFactory( "postgresql://u:p@localhost:5432/postgres", serverFactory, WithFailFast(false), ) if err != nil { t.Fatalf("NewExporter returned an error with fail-fast disabled: %v", err) } if exporter == nil { t.Fatal("NewExporter returned a nil exporter with fail-fast disabled") } if closed.Load() { t.Fatal("NewExporter closed the primary server database with fail-fast disabled") } exporter.Close() if !closed.Load() { t.Fatal("Exporter.Close did not close the primary server database") } } func TestExporterDescribeAndCloseNoPanic(t *testing.T) { s := NewServer("postgresql://u:p@localhost:5432/postgres") s.beforeScrape = func(s *Server) error { s.UP = true return nil } s.Planned = true s.Collectors = []*Collector{makeCachedCollectorForServer(s, "q", 1)} s.ResetStats() e := &Exporter{ server: s, servers: map[string]*Server{}, } e.setupInternalMetrics() descCh := make(chan *prometheus.Desc, 32) e.Describe(descCh) if len(descCh) != 0 { t.Fatalf("Describe should not emit descriptors for a dynamic/unchecked exporter, got %d", len(descCh)) } // server DB pointers are nil in this synthetic test; Close should not panic. e.Close() } func TestDisableIntroSuppressesInternalMetrics(t *testing.T) { s := NewServer("postgresql://u:p@localhost:5432/postgres") s.beforeScrape = func(s *Server) error { s.UP = true s.Version = 160000 s.Recovery = false return nil } s.Planned = true s.Collectors = []*Collector{makeCachedCollectorForServer(s, "q", 1)} s.ResetStats() e := &Exporter{ server: s, servers: map[string]*Server{}, disableIntro: true, } e.setupInternalMetrics() r := prometheus.NewRegistry() if err := r.Register(e); err != nil { t.Fatalf("register exporter failed: %v", err) } mfs, err := r.Gather() if err != nil { t.Fatalf("Gather failed: %v", err) } found := map[string]bool{} for _, mf := range mfs { found[mf.GetName()] = true } if !found["q_value"] { t.Fatalf("expected query metric q_value to be present, got: %#v", found) } // Default internal metrics namespace for postgres is "pg". if found["pg_up"] || found["pg_version"] || found["pg_in_recovery"] || found["pg_exporter_build_info"] || found["pg_exporter_up"] { t.Fatalf("disable-intro should suppress internal metrics, got: %#v", found) } } func TestServerIntrospectionHelpers(t *testing.T) { s := NewServer("postgresql://u:p@localhost:5432/postgres") c := makeCachedCollectorForServer(s, "q", 1) s.Collectors = []*Collector{c} s.ResetStats() if s.Error() != nil { t.Fatalf("new server Error should be nil, got %v", s.Error()) } if got := s.Duration(); got != 0 { t.Fatalf("new server Duration = %v, want 0", got) } if got := s.Uptime(); got < 0 { t.Fatalf("Uptime should be non-negative, got %v", got) } if got := c.ResultSize(); got != 1 { t.Fatalf("collector ResultSize = %d, want 1", got) } if skip, _ := c.PredicateSkip(); skip { t.Fatal("collector PredicateSkip should be false by default") } if got := c.Duration(); got != 0 { t.Fatalf("collector Duration = %v, want 0", got) } if exp := s.Explain(); exp == "" { t.Fatal("Explain should not be empty") } if html := s.ExplainHTML(); html == "" { t.Fatal("ExplainHTML should not be empty") } } pg_exporter-1.4.1/exporter/pgurl.go000066400000000000000000000075461523237665500174350ustar00rootroot00000000000000package exporter import ( "net/url" "os" "strings" ) // GetPGURL will retrieve, parse, modify postgres connection string func GetPGURL() string { return ProcessPGURL(RetrievePGURL()) } // RetrievePGURL retrieve pg target url from multiple sources according to precedence // priority: cli-args > env > env file path // 1. Command Line Argument (--url -u -d) // 2. Environment PG_EXPORTER_URL // 3. From file specified via Environment PG_EXPORTER_URL_FILE // 4. Default url // // The default URL intentionally targets local libpq defaults. This is a // local-first behavior for on-host deployments, where pg_exporter usually // runs on the same machine as PostgreSQL/PgBouncer. func RetrievePGURL() (res string) { // command line args if *pgURL != "" { logInfof("retrieve target url %s from command line", ShadowPGURL(*pgURL)) return *pgURL } // env PG_EXPORTER_URL if res = os.Getenv("PG_EXPORTER_URL"); res != "" { logInfof("retrieve target url %s from PG_EXPORTER_URL", ShadowPGURL(res)) return res } // env PGURL if res = os.Getenv("PGURL"); res != "" { logInfof("retrieve target url %s from PGURL", ShadowPGURL(res)) return res } // file content from file PG_EXPORTER_URL_FILE if filename := os.Getenv("PG_EXPORTER_URL_FILE"); filename != "" { if fileContents, err := os.ReadFile(filename); err != nil { logFatalf("PG_EXPORTER_URL_FILE=%s is specified, fail loading url: %s", filename, err.Error()) } else { res = strings.TrimSpace(string(fileContents)) logInfof("retrieve target url %s from PG_EXPORTER_URL_FILE", ShadowPGURL(res)) return res } } // DEFAULT logWarnf("fail retrieving target url, fallback on default url: %s", defaultPGURL) return defaultPGURL } // ProcessPGURL will fix URL with default options. // // Design decision: // If sslmode is omitted, force sslmode=disable. pg_exporter is typically // deployed as an on-host/local exporter, where TLS on loopback adds overhead // without meaningful security benefit. Users can always override by passing an // explicit sslmode in the URL. func ProcessPGURL(pgurl string) string { u, err := url.Parse(pgurl) if err != nil { logErrorf("invalid url format %s", pgurl) return "" } // add sslmode = disable if not exists qs := u.Query() if sslmode := qs.Get(`sslmode`); sslmode == "" { qs.Set(`sslmode`, `disable`) } u.RawQuery = qs.Encode() return u.String() } // ShadowPGURL will hide password part of dsn func ShadowPGURL(pgurl string) string { parsedURL, err := url.Parse(pgurl) // That means we got a bad connection string. Fail early if err != nil { logFatalf("Could not parse connection string %s", err.Error()) } // We need to handle two cases: // 1. The password is in the format postgresql://localhost:5432/postgres?sslmode=disable&user=&password= // 2. The password is in the format postgresql://:@localhost:5432/postgres?sslmode=disable qs := parsedURL.Query() for k, values := range qs { if strings.EqualFold(k, "password") { for i := range values { values[i] = "xxxxx" } qs[k] = values } } parsedURL.RawQuery = qs.Encode() return parsedURL.Redacted() } // ParseDatname extract database name part of a pgurl func ParseDatname(pgurl string) string { u, err := url.Parse(pgurl) if err != nil { return "" } if datname := strings.TrimLeft(u.Path, "/"); datname != "" { return datname } if datname := strings.TrimSpace(u.Query().Get("dbname")); datname != "" { return datname } return "" } // ReplaceDatname will replace pgurl with new database name func ReplaceDatname(pgurl, datname string) string { u, err := url.Parse(pgurl) if err != nil { logErrorf("invalid url format %s", pgurl) return "" } if strings.TrimLeft(u.Path, "/") == "" { qs := u.Query() if qs.Get("dbname") != "" { qs.Set("dbname", datname) u.RawQuery = qs.Encode() return u.String() } } u.Path = "/" + datname return u.String() } pg_exporter-1.4.1/exporter/pgurl_test.go000066400000000000000000000071601523237665500204640ustar00rootroot00000000000000package exporter import ( "net/url" "os" "path/filepath" "testing" ) func TestProcessPGURLKeepsEncodedQueryValues(t *testing.T) { input := "postgresql://user:pass@localhost:5432/postgres?application_name=a%26b&password=p%3Dq" output := ProcessPGURL(input) if output == "" { t.Fatalf("ProcessPGURL returned empty output") } parsed, err := url.Parse(output) if err != nil { t.Fatalf("failed to parse output URL: %v", err) } qs := parsed.Query() if got := qs.Get("application_name"); got != "a&b" { t.Fatalf("application_name = %q, want %q", got, "a&b") } if got := qs.Get("password"); got != "p=q" { t.Fatalf("password = %q, want %q", got, "p=q") } if got := qs.Get("sslmode"); got != "disable" { t.Fatalf("sslmode = %q, want %q", got, "disable") } } func TestShadowPGURLRedactsQueryPassword(t *testing.T) { input := "postgresql://user:pass@localhost:5432/postgres?password=p%26q%3D1&application_name=test" output := ShadowPGURL(input) parsed, err := url.Parse(output) if err != nil { t.Fatalf("failed to parse redacted URL: %v", err) } if got := parsed.Query().Get("password"); got != "xxxxx" { t.Fatalf("password = %q, want %q", got, "xxxxx") } } func TestParseDatnameAndReplaceDatname(t *testing.T) { src := "postgresql://user:pass@localhost:5432/postgres?sslmode=disable" if got := ParseDatname(src); got != "postgres" { t.Fatalf("ParseDatname = %q, want %q", got, "postgres") } replaced := ReplaceDatname(src, "otherdb") if got := ParseDatname(replaced); got != "otherdb" { t.Fatalf("ParseDatname(replaced) = %q, want %q", got, "otherdb") } srcWithDbname := "postgresql://user:pass@localhost:5432?sslmode=disable&dbname=pgbouncer" if got := ParseDatname(srcWithDbname); got != "pgbouncer" { t.Fatalf("ParseDatname(dbname=) = %q, want %q", got, "pgbouncer") } replacedDbname := ReplaceDatname(srcWithDbname, "postgres") if got := ParseDatname(replacedDbname); got != "postgres" { t.Fatalf("ParseDatname(replaced dbname=) = %q, want %q", got, "postgres") } } func TestRetrievePGURLPriority(t *testing.T) { originPGURL := *pgURL *pgURL = "" t.Cleanup(func() { *pgURL = originPGURL }) originExporterURL := os.Getenv("PG_EXPORTER_URL") originPGURLenv := os.Getenv("PGURL") originFile := os.Getenv("PG_EXPORTER_URL_FILE") t.Cleanup(func() { _ = os.Setenv("PG_EXPORTER_URL", originExporterURL) _ = os.Setenv("PGURL", originPGURLenv) _ = os.Setenv("PG_EXPORTER_URL_FILE", originFile) }) _ = os.Setenv("PG_EXPORTER_URL", "postgresql://env-user:env-pass@localhost:5432/envdb") _ = os.Setenv("PGURL", "postgresql://pgurl-user:pgurl-pass@localhost:5432/pgurldb") *pgURL = "postgresql://cli-user:cli-pass@localhost:5432/clidb" if got := RetrievePGURL(); got != *pgURL { t.Fatalf("RetrievePGURL CLI precedence failed: got %s", got) } *pgURL = "" if got := RetrievePGURL(); got != os.Getenv("PG_EXPORTER_URL") { t.Fatalf("RetrievePGURL env precedence failed: got %s", got) } _ = os.Unsetenv("PG_EXPORTER_URL") if got := RetrievePGURL(); got != os.Getenv("PGURL") { t.Fatalf("RetrievePGURL PGURL fallback failed: got %s", got) } _ = os.Unsetenv("PGURL") file := filepath.Join(t.TempDir(), "dsn.txt") fileURL := "postgresql://file-user:file-pass@localhost:5432/filedb" if err := os.WriteFile(file, []byte(fileURL), 0o644); err != nil { t.Fatalf("write dsn file failed: %v", err) } _ = os.Setenv("PG_EXPORTER_URL_FILE", file) if got := RetrievePGURL(); got != fileURL { t.Fatalf("RetrievePGURL file fallback failed: got %s", got) } _ = os.Unsetenv("PG_EXPORTER_URL_FILE") if got := RetrievePGURL(); got != defaultPGURL { t.Fatalf("RetrievePGURL default fallback failed: got %s", got) } } pg_exporter-1.4.1/exporter/predicate_cache_test.go000066400000000000000000000041221523237665500224110ustar00rootroot00000000000000package exporter import ( "context" "testing" "time" ) func TestPredicateCacheHitSkipsDBQuery(t *testing.T) { q := &Query{ Name: "q", Branch: "q", PredicateQueries: []PredicateQuery{ {Name: "p1", SQL: "SELECT true", TTL: 10}, }, } s := &Server{Database: "postgres"} // DB is nil; any QueryContext call would panic. c := NewCollector(q, s) now := time.Now() c.scrapeBegin = now // Cache hit: pass=true should continue and ultimately return true without touching DB. c.predicateCache[0] = predicateCacheEntry{at: now.Add(-time.Second), pass: true} if ok := c.executePredicateQueries(context.Background()); !ok { t.Fatal("expected cached pass=true to allow query execution") } // Cache hit: pass=false should return false without touching DB. c.scrapeBegin = now c.predicateCache[0] = predicateCacheEntry{at: now.Add(-time.Second), pass: false} if ok := c.executePredicateQueries(context.Background()); ok { t.Fatal("expected cached pass=false to skip query execution") } } func TestPredicateCacheMissTriggersDBQuery(t *testing.T) { q := &Query{ Name: "q", Branch: "q", PredicateQueries: []PredicateQuery{ {Name: "p1", SQL: "SELECT true", TTL: 10}, }, } s := &Server{Database: "postgres"} // DB is nil; QueryContext must panic if called. c := NewCollector(q, s) c.scrapeBegin = time.Now() defer func() { if r := recover(); r == nil { t.Fatal("expected panic due to DB access on predicate cache miss") } }() _ = c.executePredicateQueries(context.Background()) } func TestPredicateCacheDisabledByTTLZero(t *testing.T) { q := &Query{ Name: "q", Branch: "q", PredicateQueries: []PredicateQuery{ {Name: "p1", SQL: "SELECT true", TTL: 0}, }, } s := &Server{Database: "postgres"} // DB is nil; QueryContext must panic if called. c := NewCollector(q, s) c.scrapeBegin = time.Now() c.predicateCache[0] = predicateCacheEntry{at: time.Now(), pass: true} defer func() { if r := recover(); r == nil { t.Fatal("expected panic due to DB access when predicate TTL is 0 (cache disabled)") } }() _ = c.executePredicateQueries(context.Background()) } pg_exporter-1.4.1/exporter/probehealth_pgbouncer_test.go000066400000000000000000000044151523237665500236740ustar00rootroot00000000000000package exporter import ( "context" "database/sql" "database/sql/driver" "errors" "io" "testing" ) var errProbeHealthTestPingCalled = errors.New("ping called") // probeHealthTestDriver is a tiny database/sql driver used to verify that // ProbeHealth in pgbouncer mode does not use db.PingContext (lib/pq Ping uses // a ";" query which PgBouncer rejects). If Ping is called we return a sentinel // error to fail the test. type probeHealthTestDriver struct{} func (d probeHealthTestDriver) Open(name string) (driver.Conn, error) { return &probeHealthTestConn{}, nil } type probeHealthTestConn struct{} func (c *probeHealthTestConn) Prepare(query string) (driver.Stmt, error) { return nil, errors.New("prepare not supported") } func (c *probeHealthTestConn) Close() error { return nil } func (c *probeHealthTestConn) Begin() (driver.Tx, error) { return nil, errors.New("tx not supported") } func (c *probeHealthTestConn) Ping(ctx context.Context) error { return errProbeHealthTestPingCalled } func (c *probeHealthTestConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { // Return an empty resultset; database/sql will surface this as sql.ErrNoRows // to QueryRowContext, which ProbeHealth should treat as a successful probe // for PgBouncer (SHOW VERSION may return via NOTICE only). return &probeHealthTestRows{}, nil } type probeHealthTestRows struct{} func (r *probeHealthTestRows) Columns() []string { return []string{"version"} } func (r *probeHealthTestRows) Close() error { return nil } func (r *probeHealthTestRows) Next(dest []driver.Value) error { return io.EOF } func init() { sql.Register("probehealth_test", probeHealthTestDriver{}) } func TestProbeHealthPgbouncerDoesNotPingAndTreatsNoRowsAsUp(t *testing.T) { db, err := sql.Open("probehealth_test", "") if err != nil { t.Fatalf("sql.Open: %v", err) } defer db.Close() s := &Server{ DB: db, PgbouncerMode: true, ConnectTimeout: 500, } up, recovery, starting, err := s.ProbeHealth() if err != nil { t.Fatalf("ProbeHealth error = %v", err) } if !up { t.Fatalf("up = %v, want true", up) } if recovery { t.Fatalf("recovery = %v, want false", recovery) } if starting { t.Fatalf("starting = %v, want false", starting) } } pg_exporter-1.4.1/exporter/prom_validate.go000066400000000000000000000013001523237665500211100ustar00rootroot00000000000000package exporter import ( "fmt" "strings" "github.com/prometheus/common/model" ) func validatePromLabelName(name string) error { if name == "" { return fmt.Errorf("empty label name") } if strings.HasPrefix(name, model.ReservedLabelPrefix) { return fmt.Errorf("label name %q uses reserved prefix %q", name, model.ReservedLabelPrefix) } if !model.LegacyValidation.IsValidLabelName(name) { return fmt.Errorf("invalid label name %q", name) } return nil } func validatePromMetricName(name string) error { if name == "" { return fmt.Errorf("empty metric name") } if !model.LegacyValidation.IsValidMetricName(name) { return fmt.Errorf("invalid metric name %q", name) } return nil } pg_exporter-1.4.1/exporter/query.go000066400000000000000000000171211523237665500174370ustar00rootroot00000000000000package exporter import ( "bytes" "fmt" htmltmpl "html/template" "slices" texttmpl "text/template" "time" "gopkg.in/yaml.v3" ) /* ================ Query ================ */ // Query hold the information of how to fetch metric and parse them type Query struct { Name string `yaml:"name,omitempty"` // actual query name, used as metric prefix Desc string `yaml:"desc,omitempty"` // description of this metric query SQL string `yaml:"query"` // SQL command to fetch metrics PredicateQueries []PredicateQuery `yaml:"predicate_queries,omitempty"` // SQL command to filter metrics Branch string `yaml:"-"` // branch name, top layer key of config file // control query behaviour Tags []string `yaml:"tags,omitempty"` // tags are used for execution control TTL float64 `yaml:"ttl,omitempty"` // caching ttl in seconds Timeout float64 `yaml:"timeout,omitempty"` // query execution timeout in seconds Priority int `yaml:"priority,omitempty"` // execution priority, from 1 to 999 MinVersion int `yaml:"min_version,omitempty"` // minimal supported version, include MaxVersion int `yaml:"max_version,omitempty"` // maximal supported version, not include Fatal bool `yaml:"fatal,omitempty"` // if query marked fatal fail, entire scrape will fail Skip bool `yaml:"skip,omitempty"` // if query marked skip, it will be omit while loading Metrics []map[string]*Column `yaml:"metrics"` // metric definition list // metrics parsing auxiliaries Path string `yaml:"-"` // where am I from ? Columns map[string]*Column `yaml:"-"` // column map ColumnNames []string `yaml:"-"` // column names in origin orders LabelNames []string `yaml:"-"` // column (name) that used as label, sequences matters MetricNames []string `yaml:"-"` // column (name) that used as metric } // A PredicateQuery is a query that returns a 1-column resultset that's used to decide whether // to run the main query. type PredicateQuery struct { Name string `yaml:"name,omitempty"` // predicate query name, only used for logging SQL string `yaml:"predicate_query"` // SQL command to return a predicate TTL float64 `yaml:"ttl,omitempty"` // How long to cache results for } var queryTemplate, _ = texttmpl.New("Query").Parse(`## # SYNOPSIS # {{ .Name }}{{ if ne .Name .Branch }}.{{ .Branch }}{{ end }}_* # # DESCRIPTION # {{ with .Desc }}{{ . }}{{ else }}N/A{{ end }} # # OPTIONS # Tags [{{ range $i, $e := .Tags }}{{ if $i }}, {{ end }}{{ $e }}{{ end }}] # TTL {{ .TTL }} # Priority {{ .Priority }} # Timeout {{ .TimeoutDuration }} # Fatal {{ .Fatal }} # Version {{ if ne .MinVersion 0 }}{{ .MinVersion }}{{ else }}lower{{ end }} ~ {{ if ne .MaxVersion 0 }}{{ .MaxVersion }}{{ else }}higher{{ end }} # Source {{ .Path }} # # METRICS {{- range .ColumnList }} # {{ .Name }} ({{ .Usage }}) # {{ with .Desc }}{{ . }}{{ else }}N/A{{ end }}{{ if .Bucket }} # Bucket {{ .Bucket }}{{ end }}{{ end }} # {{.MarshalYAML -}} `) var htmlTemplate, _ = htmltmpl.New("Query").Parse(`

{{ .Name }}

{{ .Desc }}

{{ if len .PredicateQueries }}

Predicate queries

{{ range .PredicateQueries }} {{ end }}
Name SQL Cache TTL
{{ .Name }}{{ .SQL }}{{if ne .TTL 0}}{{ .TTL }}s{{else}}not cached{{end}}
{{ end }}

Query

{{ .SQL }}

Attribution

Branch {{ .Branch }}
TTL {{ .TTL }}
Priority {{ .Priority }}
Timeout {{ .TimeoutDuration }}
Fatal {{ .Fatal }}
Version {{if ne .MinVersion 0}}{{ .MinVersion }}{{else}}lower{{end}} ~ {{if ne .MaxVersion 0}}{{ .MaxVersion }}{{else}}higher{{end}}
Tags {{ .Tags }}
Source {{ .Path }}

Columns

{{ range .ColumnList }}{{ end }}
Name Usage Rename Bucket Scale Default Description
{{ .Name }}{{ .Usage }}{{ .Rename }}{{ .Bucket }}{{ .Scale }}{{ .Default }}{{ .Desc }}

Metrics

{{ range .MetricList }}{{ end }}
Name Usage Desc
{{ .Name }}{{ .Column.Usage }}{{ .Column.Desc }}
`) // MarshalYAML will turn query into YAML format func (q *Query) MarshalYAML() string { v := make(map[string]Query, 1) v[q.Branch] = *q buf, err := yaml.Marshal(v) if err != nil { msg := fmt.Sprintf("fail to marshall query yaml: %s", err.Error()) logError(msg) return msg } return string(buf) } // Explain will turn query into text format func (q *Query) Explain() string { buf := new(bytes.Buffer) err := queryTemplate.Execute(buf, q) if err != nil { msg := fmt.Sprintf("fail to explain query: %s", err.Error()) logError(msg) return msg } return buf.String() } // HTML will turn Query into HTML format func (q *Query) HTML() string { buf := new(bytes.Buffer) err := htmlTemplate.Execute(buf, q) if err != nil { msg := fmt.Sprintf("fail to generate query html: %s", err.Error()) logError(msg) return msg } return buf.String() } // HasTag tells whether this query have specific tag // since only few tags is provided, we don't really need a map here func (q *Query) HasTag(tag string) bool { return slices.Contains(q.Tags, tag) } // ColumnList return ordered column list func (q *Query) ColumnList() (res []*Column) { res = make([]*Column, len(q.ColumnNames)) for i, colName := range q.ColumnNames { res[i] = q.Columns[colName] } return } // LabelList returns a list of label column names func (q *Query) LabelList() []string { labelNames := make([]string, len(q.LabelNames)) for i, labelName := range q.LabelNames { labelColumn := q.Columns[labelName] if labelColumn.Rename != "" { labelNames[i] = labelColumn.Rename } else { labelNames[i] = labelColumn.Name } } return labelNames } // MetricList returns a list of MetricDesc generated by this query func (q *Query) MetricList() (res []*MetricDesc) { res = make([]*MetricDesc, len(q.MetricNames)) for i, metricName := range q.MetricNames { column := q.Columns[metricName] res[i] = column.MetricDesc(q.Name, q.LabelList()) } return } // HasHistogram reports whether this query defines at least one logical // Histogram metric. Histogram components are derived later by the collector. func (q *Query) HasHistogram() bool { for _, metricName := range q.MetricNames { if column := q.Columns[metricName]; column != nil && column.IsHistogram() { return true } } return false } // TimeoutDuration will turn timeout settings into time.Duration func (q *Query) TimeoutDuration() time.Duration { return time.Duration(float64(time.Second) * q.Timeout) } pg_exporter-1.4.1/exporter/query_column_test.go000066400000000000000000000062401523237665500220530ustar00rootroot00000000000000package exporter import ( "strings" "testing" "time" "github.com/prometheus/client_golang/prometheus" ) func makeSampleQuery() *Query { return &Query{ Name: "sample", Branch: "sample_branch", Desc: "sample query", Tags: []string{"tag1", "tag2"}, Timeout: 1.5, Columns: map[string]*Column{ "datname": { Name: "datname", Usage: LABEL, Rename: "db", Desc: "database name", }, "value": { Name: "value", Usage: GAUGE, Rename: "val", Desc: "metric value", }, }, ColumnNames: []string{"datname", "value"}, LabelNames: []string{"datname"}, MetricNames: []string{"value"}, Metrics: []map[string]*Column{ {"datname": {Name: "datname", Usage: LABEL, Rename: "db", Desc: "database name"}}, {"value": {Name: "value", Usage: GAUGE, Rename: "val", Desc: "metric value"}}, }, } } func TestColumnPrometheusValueType(t *testing.T) { cGauge := &Column{Name: "g", Usage: GAUGE} if got := cGauge.PrometheusValueType(); got != prometheus.GaugeValue { t.Fatalf("gauge type = %v, want GaugeValue", got) } cCounter := &Column{Name: "c", Usage: COUNTER} if got := cCounter.PrometheusValueType(); got != prometheus.CounterValue { t.Fatalf("counter type = %v, want CounterValue", got) } defer func() { if r := recover(); r == nil { t.Fatal("PrometheusValueType should panic for non-value usage") } }() _ = (&Column{Name: "x", Usage: LABEL}).PrometheusValueType() } func TestColumnAndMetricDescString(t *testing.T) { c := &Column{Name: "value", Usage: GAUGE, Desc: "desc"} if !strings.Contains(c.String(), "value") { t.Fatalf("column string does not contain name: %s", c.String()) } md := c.MetricDesc("sample", []string{"db"}) if !strings.Contains(md.Name, "sample_value") { t.Fatalf("metric desc name = %s", md.Name) } if !strings.Contains(md.Name, "{db}") { t.Fatalf("metric desc should contain labels signature, got %s", md.Name) } if !strings.Contains(md.String(), "desc") { t.Fatalf("metric desc string = %s", md.String()) } } func TestQueryHelpersAndRender(t *testing.T) { q := makeSampleQuery() if !q.HasTag("tag1") || q.HasTag("missing") { t.Fatalf("HasTag result unexpected for tags %v", q.Tags) } cols := q.ColumnList() if len(cols) != 2 || cols[0].Name != "datname" || cols[1].Name != "value" { t.Fatalf("ColumnList order unexpected: %#v", cols) } labels := q.LabelList() if len(labels) != 1 || labels[0] != "db" { t.Fatalf("LabelList = %#v, want [db]", labels) } metrics := q.MetricList() if len(metrics) != 1 { t.Fatalf("MetricList len = %d, want 1", len(metrics)) } if !strings.Contains(metrics[0].Name, "sample_val") { t.Fatalf("MetricList name = %s", metrics[0].Name) } if got := q.TimeoutDuration(); got != 1500*time.Millisecond { t.Fatalf("TimeoutDuration = %v, want 1500ms", got) } yaml := q.MarshalYAML() if !strings.Contains(yaml, "sample_branch:") { t.Fatalf("MarshalYAML missing branch key: %s", yaml) } explain := q.Explain() if !strings.Contains(explain, "SYNOPSIS") { t.Fatalf("Explain output unexpected: %s", explain) } html := q.HTML() if !strings.Contains(html, "

sample

") { t.Fatalf("HTML output unexpected: %s", html) } } pg_exporter-1.4.1/exporter/reload_signals_unix.go000066400000000000000000000002051523237665500223160ustar00rootroot00000000000000//go:build !windows package exporter import ( "os" "syscall" ) var reloadSignals = []os.Signal{syscall.SIGHUP, syscall.SIGUSR1} pg_exporter-1.4.1/exporter/reload_signals_windows.go000066400000000000000000000001631523237665500230300ustar00rootroot00000000000000//go:build windows package exporter import ( "os" "syscall" ) var reloadSignals = []os.Signal{syscall.SIGHUP} pg_exporter-1.4.1/exporter/reload_test.go000066400000000000000000000032551523237665500206020ustar00rootroot00000000000000package exporter import ( "os" "path/filepath" "testing" ) func TestReloadUpdatesQueriesInPlace(t *testing.T) { originExporter := PgExporter t.Cleanup(func() { setCurrentExporter(originExporter) }) originConfigPath := *configPath t.Cleanup(func() { *configPath = originConfigPath }) // Seed exporter with an initial query set and a planned server. s := NewServer("postgresql://u:p@localhost:5432/postgres") s.beforeScrape = func(s *Server) error { return nil } s.Planned = true s.queries = map[string]*Query{"old": makeGaugeQuery("old", 1)} s.Collectors = []*Collector{NewCollector(makeGaugeQuery("old", 1), s)} s.ResetStats() e := &Exporter{ server: s, servers: map[string]*Server{}, queries: s.queries, } setCurrentExporter(e) // Write a new config and reload it. dir := t.TempDir() cfgPath := filepath.Join(dir, "pg_exporter.yml") cfg := ` q_new: query: SELECT 1 AS value, 'db' AS datname metrics: - datname: usage: label description: db - value: usage: gauge description: value ` if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil { t.Fatalf("write config failed: %v", err) } *configPath = cfgPath if err := Reload(); err != nil { t.Fatalf("Reload failed: %v", err) } if _, ok := e.queries["q_new"]; !ok { t.Fatalf("expected new query to be loaded, got: %#v", e.queries) } if e.server.queries == nil || e.server.queries["q_new"] == nil { t.Fatalf("server queries not updated, got: %#v", e.server.queries) } if e.server.Planned { t.Fatalf("server should be marked unplanned after reload") } if e.server.Collectors != nil { t.Fatalf("server collectors should be cleared after reload") } } pg_exporter-1.4.1/exporter/server.go000066400000000000000000000600241523237665500176000ustar00rootroot00000000000000package exporter import ( "bytes" "context" "database/sql" "errors" "fmt" "regexp" "sort" "strconv" "strings" "sync" "time" "github.com/lib/pq" "github.com/prometheus/client_golang/prometheus" ) /* ================ Const ================ */ const connMaxLifeTime = 1 * time.Minute // close connection after 1 minute to avoid conn leak const pgSQLStateCannotConnectNow = "57P03" /* ================ Server ================ */ var semverRe = regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`) // Server represent a postgres connection, with additional fact, conf, runtime info type Server struct { *sql.DB // database instance (do not close this due to the stupid implementation in database/sql) dsn string // data source name lock sync.RWMutex // server scrape lock err error // last error // notice handling (primarily for pgbouncer, where SHOW VERSION may return via NOTICE) noticeMu sync.Mutex lastNotice string // hooks beforeScrape func(s *Server) error // hook: execute before scrape onDatabaseChange func(change map[string]bool) // hook: invoke when database list is changed // postgres fact gather from server UP bool // indicate whether target server is connectable Recovery bool // is server in recovering Version int // pg server version num Database string // database name of current server connection Username string // current username Databases map[string]bool // all available database in target cluster dblistLock sync.Mutex // lock when access Databases map Namespaces map[string]bool // all available schema in target cluster Extensions map[string]bool // all available extension in target cluster Tags []string // server tags set by cli arg --tag PgbouncerMode bool // indicate it is a pgbouncer server DisableCache bool // force executing, ignoring caching policy ExcludeDbnames []string // if ExcludeDbnames is provided, Auto Database Discovery is enabled Forked bool // is this a forked server ? (does not run cluster level query) Planned bool // if false, server will trigger a plan before collect MaxConn int // max connection for this server ConnectTimeout int // connect timeout for this server in ms ConnMaxLifetime int // connection max lifetime for this server in seconds // query Collectors []*Collector // query collector instance (installed query) queries map[string]*Query // queries map, keys are config file top layer key labels prometheus.Labels // constant labels // internal stats serverInit time.Time // server init timestamp scrapeBegin time.Time // server last scrape begin time scrapeDone time.Time // server last scrape done time errorCount float64 // total scrape error count on this server (fatal scrape failures only) totalCount float64 // total scrape count on this server totalTime float64 // total time spend on scraping queryCacheTTL map[string]float64 // internal query metrics: cache time to live queryScrapeTotalCount map[string]float64 // internal query metrics: total executed queryScrapeHitCount map[string]float64 // internal query metrics: times serving from hit cache queryScrapeErrorCount map[string]float64 // internal query metrics: times failed queryScrapePredicateSkipCount map[string]float64 // internal query metrics: times skipped due to predicate queryScrapeMetricCount map[string]float64 // internal query metrics: number of metrics scraped queryScrapeDuration map[string]float64 // internal query metrics: time spend on executing } func (s *Server) GetConnectTimeout() time.Duration { if s.ConnectTimeout <= 0 { return 100 * time.Millisecond } return time.Duration(s.ConnectTimeout) * time.Millisecond } // Name is coalesce(s.Database, dsn) func (s *Server) Name() string { if s.Database != "" { return s.Database } return ShadowPGURL(s.dsn) } func (s *Server) Error() error { return s.err } // Check will issue a connection and executing precheck hook function func (s *Server) Check() error { s.lock.Lock() defer s.lock.Unlock() return s.beforeScrape(s) } // ProbeHealth performs a lightweight probe for exporter health checks. // It returns whether the database is reachable, whether it's in recovery, // and whether PostgreSQL is still starting up (SQLSTATE 57P03). func (s *Server) ProbeHealth() (up, recovery, starting bool, err error) { // Snapshot pointers/flags quickly under lock to avoid races with first-time DB init. s.lock.RLock() db := s.DB pgbouncerMode := s.PgbouncerMode timeout := s.GetConnectTimeout() s.lock.RUnlock() if db == nil { return false, false, false, errors.New("database connection is not initialized") } ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() if pgbouncerMode { // IMPORTANT: do NOT use db.PingContext() for PgBouncer. // // lib/pq implements Ping() by sending a simpleQuery(";") (empty command). // PostgreSQL tolerates that, but PgBouncer rejects it with: // invalid command ';', use SHOW HELP; // This becomes a log-spam regression once ProbeHealth is run periodically. // // For health probes we only need a cheap PgBouncer admin command. // Some PgBouncer versions return SHOW VERSION via NOTICE (no result rows), // so treat sql.ErrNoRows as success. var dummy string qerr := db.QueryRowContext(ctx, `SHOW VERSION;`).Scan(&dummy) if qerr != nil && !errors.Is(qerr, sql.ErrNoRows) { return false, false, false, qerr } return true, false, false, nil } if err = db.QueryRowContext(ctx, `SELECT pg_catalog.pg_is_in_recovery();`).Scan(&recovery); err != nil { starting = isPostgresStartupError(err) return false, false, starting, err } return true, recovery, false, nil } func isPostgresStartupError(err error) bool { var pgErr *pq.Error return errors.As(err, &pgErr) && string(pgErr.Code) == pgSQLStateCannotConnectNow } // PgbouncerPrecheck checks pgbouncer connection before scrape func PgbouncerPrecheck(s *Server) (err error) { if s.DB == nil { // if db is not initialized, create a new DB with a NOTICE handler base, cerr := pq.NewConnector(s.dsn) if cerr != nil { s.UP = false return cerr } connector := pq.ConnectorWithNoticeHandler(base, func(notice *pq.Error) { s.noticeMu.Lock() s.lastNotice = notice.Message s.noticeMu.Unlock() }) s.DB = sql.OpenDB(connector) s.DB.SetMaxIdleConns(1) s.DB.SetMaxOpenConns(1) s.DB.SetConnMaxLifetime(connMaxLifeTime) } // Clear last notice before issuing SHOW VERSION so we can reliably parse it. s.noticeMu.Lock() s.lastNotice = "" s.noticeMu.Unlock() ctx, cancel := context.WithTimeout(context.Background(), s.GetConnectTimeout()) defer cancel() var versionStr string qerr := s.DB.QueryRowContext(ctx, `SHOW VERSION;`).Scan(&versionStr) if qerr != nil && !errors.Is(qerr, sql.ErrNoRows) { // Connection/auth errors should fail precheck (and thus scrape). s.UP = false return fmt.Errorf("fail fetching pgbouncer version: %w", qerr) } s.UP = true // Version may come from: // 1) a normal 1-row resultset (older versions / some builds), or // 2) a server NOTICE message (pgbouncer 1.12+). newVer := 0 if qerr == nil { newVer = ParseSemver(versionStr) } if newVer == 0 { s.noticeMu.Lock() notice := s.lastNotice s.noticeMu.Unlock() if notice != "" { newVer = ParseSemver(notice) } } if newVer != 0 { if s.Version != newVer { logInfof("server [%s] pgbouncer version changed: from [%d] to [%d]", s.Name(), s.Version, newVer) s.Planned = false } s.Version = newVer } else if qerr == nil || errors.Is(qerr, sql.ErrNoRows) { // Connected, but couldn't parse version string. logWarnf("server [%s] connected but failed to parse pgbouncer version (row=%q)", s.Name(), versionStr) } return nil } // ParseSemver will turn semantic version string into integer func ParseSemver(semverStr string) int { semver := semverRe.FindStringSubmatch(semverStr) logDebugf("parse pgbouncer semver string %s", semverStr) if len(semver) != 4 { return 0 } verNum := 0 if major, err := strconv.Atoi(semver[1]); err != nil { return 0 } else { verNum += major * 10000 } if minor, err := strconv.Atoi(semver[2]); err != nil { return 0 } else { verNum += minor * 100 } if release, err := strconv.Atoi(semver[3]); err != nil { return 0 } else { verNum += release } return verNum } // PostgresPrecheck checks postgres connection and gathering facts // if any important fact changed, it will trigger a plan before next scrape func PostgresPrecheck(s *Server) (err error) { if s.DB == nil { // if db is not initialized, create a new DB if s.DB, err = sql.Open("postgres", s.dsn); err != nil { s.UP = false return } if s.Forked { s.MaxConn = 1 s.DB.SetMaxIdleConns(1) s.DB.SetMaxOpenConns(1) s.DB.SetConnMaxLifetime(connMaxLifeTime) } else { s.MaxConn = 3 s.DB.SetMaxIdleConns(3) s.DB.SetMaxOpenConns(3) s.DB.SetConnMaxLifetime(1 * time.Minute) } } // retrieve version info var version int ctx, cancel := context.WithTimeout(context.Background(), s.GetConnectTimeout()) defer cancel() if err = s.DB.QueryRowContext(ctx, `SHOW server_version_num;`).Scan(&version); err != nil { s.UP = false return fmt.Errorf("fail fetching server version: %w", err) } s.UP = true // fact change triggers a new planning if s.Version != version { logInfof("server [%s] version changed: from [%d] to [%d]", s.Name(), s.Version, version) s.Planned = false } s.Version = version ctxSet, cancelSet := context.WithTimeout(context.Background(), s.GetConnectTimeout()) defer cancelSet() if _, err = s.DB.ExecContext(ctxSet, `SET application_name = pg_exporter;`); err != nil { s.UP = false return fmt.Errorf("fail setting application name: %w", err) } // get important metadata var recovery bool var datname, username string var databases, namespaces, extensions []string precheckSQL := `SELECT current_catalog, current_user, pg_catalog.pg_is_in_recovery(), (SELECT pg_catalog.array_agg(d.datname)::text[] AS databases FROM pg_catalog.pg_database d WHERE d.datallowconn AND NOT d.datistemplate), (SELECT pg_catalog.array_agg(n.nspname)::text[] AS namespaces FROM pg_catalog.pg_namespace n), (SELECT pg_catalog.array_agg(e.extname)::text[] AS extensions FROM pg_catalog.pg_extension e);` ctx2, cancel2 := context.WithTimeout(context.Background(), s.GetConnectTimeout()) defer cancel2() if err = s.DB.QueryRowContext(ctx2, precheckSQL).Scan(&datname, &username, &recovery, pq.Array(&databases), pq.Array(&namespaces), pq.Array(&extensions)); err != nil { s.UP = false return fmt.Errorf("fail fetching server version: %w", err) } if s.Recovery != recovery { logInfof("server [%s] recovery status changed: from [%v] to [%v]", s.Name(), s.Recovery, recovery) s.Planned = false } s.Recovery = recovery s.Username = username if s.Database != datname { logInfof("server [%s] datname changed: from [%s] to [%s]", s.Name(), s.Database, datname) s.Planned = false } s.Database = datname s.Databases[datname] = true // update schema & extension list s.Namespaces = make(map[string]bool, len(namespaces)) for _, nsname := range namespaces { s.Namespaces[nsname] = true } s.Extensions = make(map[string]bool, len(extensions)) for _, extname := range extensions { s.Extensions[extname] = true } // detect db change s.dblistLock.Lock() defer s.dblistLock.Unlock() newDBList := make(map[string]bool, len(databases)) changes := make(map[string]bool) // if new db is not found in old db list, add a change entry [NewDBName:true] for _, dbname := range databases { newDBList[dbname] = true if _, found := s.Databases[dbname]; !found { logDebugf("server [%s] found new database %s", s.Name(), dbname) changes[dbname] = true } } // if old db is not found in new db list, add a change entry [OldDBName:false] for dbname := range s.Databases { if _, found := newDBList[dbname]; !found { logDebugf("server [%s] found vanished database %s", s.Name(), dbname) changes[dbname] = false } } // invoke hook if there are changes on database list if len(changes) > 0 && s.onDatabaseChange != nil { logDebugf("server [%s] auto discovery database list change : %v", s.Name(), changes) s.onDatabaseChange(changes) // if doing something long, launch another goroutine } s.Databases = newDBList return nil } // Plan will install queries that compatible with server fact (version, level, recovery, plugin, tags,...) func (s *Server) Plan(queries ...*Query) { // if queries are explicitly given, use it instead of server.queries if len(queries) > 0 { newQueries := make(map[string]*Query) for _, q := range queries { newQueries[q.Name] = q } s.queries = newQueries } // check query compatibility instances := make([]*Collector, 0) var installedNames, discardedNames []string for name, query := range s.queries { if ok, reason := s.Compatible(query); ok { instances = append(instances, NewCollector(query, s)) installedNames = append(installedNames, query.Branch) } else { discardedNames = append(discardedNames, query.Branch) logDebugf("query [%s].%s discarded because of %s", query.Name, name, reason) } } // sort by priority sort.Slice(instances, func(i, j int) bool { return instances[i].Priority < instances[j].Priority }) s.Collectors = instances // reset statistics after planning s.ResetStats() s.Planned = true logInfof("server [%s] planned with %d queries, %d installed, %d discarded, installed: %s , discarded: %s", s.Name(), len(s.queries), len(installedNames), len(discardedNames), strings.Join(installedNames, ", "), strings.Join(discardedNames, ", ")) } // ResetStats will clear all statistic info func (s *Server) ResetStats() { n := len(s.Collectors) s.queryCacheTTL = make(map[string]float64, n) s.queryScrapeTotalCount = make(map[string]float64, n) s.queryScrapeHitCount = make(map[string]float64, n) s.queryScrapeErrorCount = make(map[string]float64, n) s.queryScrapePredicateSkipCount = make(map[string]float64, n) s.queryScrapeMetricCount = make(map[string]float64, n) s.queryScrapeDuration = make(map[string]float64, n) for _, query := range s.Collectors { s.queryCacheTTL[query.Name] = 0 s.queryScrapeTotalCount[query.Name] = 0 s.queryScrapeHitCount[query.Name] = 0 s.queryScrapeErrorCount[query.Name] = 0 if len(query.PredicateQueries) > 0 { s.queryScrapePredicateSkipCount[query.Name] = 0 } s.queryScrapeMetricCount[query.Name] = 0 s.queryScrapeDuration[query.Name] = 0 } } // Compatible tells whether a query is compatible with current server func (s *Server) Compatible(query *Query) (res bool, reason string) { // check skip flag if query.Skip { return false, fmt.Sprintf("query %s is marked skip", query.Name) } // check mode if pgbouncerQuery := query.HasTag("pgbouncer"); pgbouncerQuery != s.PgbouncerMode { if s.PgbouncerMode { return false, fmt.Sprintf("pgbouncer server doese not match with normal postgres query %s", query.Name) } return false, fmt.Sprintf("pgbouncer query %s does not match with normal postgres server", query.Name) } // check version if s.Version != 0 { // if version is not determined yet, just let it go if query.MinVersion != 0 && s.Version < query.MinVersion { return false, fmt.Sprintf("server version %v lower than query min version %v", s.Version, query.MinVersion) } if query.MaxVersion != 0 && s.Version >= query.MaxVersion { // exclude return false, fmt.Sprintf("server version %v higher than query max version %v", s.Version, query.MaxVersion) } } // check query side tags for _, tag := range query.Tags { // check extension is installed on target database if strings.HasPrefix(tag, "extension:") { if _, found := s.Extensions[strings.TrimPrefix(tag, "extension:")]; !found { return false, fmt.Sprintf("server [%s] does not have extension %s", s.Name(), tag) } continue } // check schema exist on target database if strings.HasPrefix(tag, "schema:") { if _, found := s.Namespaces[strings.TrimPrefix(tag, "schema:")]; !found { return false, fmt.Sprintf("server [%s] does not have schema %s", s.Name(), tag) } continue } // check if dbname prefix tag match server.Database if strings.HasPrefix(tag, "dbname:") { if s.Database != strings.TrimPrefix(tag, "dbname:") { return false, fmt.Sprintf("server [%s] dbname does %s not match with query tag %s", s.Name(), s.Database, tag) } continue } // check if username prefix tag match server.Username if strings.HasPrefix(tag, "username:") { if s.Username != strings.TrimPrefix(tag, "username:") { return false, fmt.Sprintf("server [%s] username [%s] does not match %s", s.Name(), s.Username, tag) } continue } // check server does not have given tag if strings.HasPrefix(tag, "not:") { if negTag := strings.TrimPrefix(tag, "not:"); s.HasTag(negTag) { return false, fmt.Sprintf("server [%s] has tag %s that query %s forbid", s.Name(), negTag, query.Name) } continue } // check 3 default tags: cluster, primary, standby|replica switch tag { case "cluster": if s.Forked { return false, fmt.Sprintf("cluster level query %s will not run on forked server %v", query.Name, s.Name()) } continue case "primary", "master", "leader": if s.Recovery { return false, fmt.Sprintf("primary-only query %s will not run on standby server %v", query.Name, s.Name()) } continue case "standby", "replica", "slave": if !s.Recovery { return false, fmt.Sprintf("standby-only query %s will not run on primary server %v", query.Name, s.Name()) } continue case "pgbouncer": continue default: // if this tag is nether a pre-defined tag nor a prefixed pattern tag, check whether server have that tag if !s.HasTag(tag) { return false, fmt.Sprintf("server [%s] does not have tag %s that query %s require", s.Name(), tag, query.Name) } } } return true, "" } // Explain will print all queries that registered to server func (s *Server) Explain() string { s.lock.RLock() defer s.lock.RUnlock() var res []string for _, i := range s.Collectors { res = append(res, i.Explain()) } return strings.Join(res, "\n") } // Stat will turn Server internal stats into HTML func (s *Server) Stat() string { s.lock.RLock() defer s.lock.RUnlock() buf := new(bytes.Buffer) buf.WriteString(fmt.Sprintf("%-24s %-10s %-10s %-10s %-10s %-10s %-6s %-10s\n", "name", "total", "hit", "error", "skip", "metric", "ttl/s", "duration/ms")) for _, query := range s.Collectors { buf.WriteString(fmt.Sprintf("%-24s %-10d %-10d %-10d %-10d %-10d %-6d %-10f\n", query.Name, int(s.queryScrapeTotalCount[query.Name]), int(s.queryScrapeHitCount[query.Name]), int(s.queryScrapeErrorCount[query.Name]), int(s.queryScrapePredicateSkipCount[query.Name]), int(s.queryScrapeMetricCount[query.Name]), int(s.queryCacheTTL[query.Name]), s.queryScrapeDuration[query.Name]*1000, )) } return buf.String() } // ExplainHTML will print server stats in HTML format func (s *Server) ExplainHTML() string { s.lock.RLock() defer s.lock.RUnlock() var res []string for _, i := range s.Collectors { res = append(res, i.HTML()) } return strings.Join(res, "

") } // Describe implement prometheus.Collector func (s *Server) Describe(ch chan<- *prometheus.Desc) { s.lock.RLock() defer s.lock.RUnlock() for _, instance := range s.Collectors { instance.Describe(ch) } } // Collect implement prometheus.Collector interface func (s *Server) Collect(ch chan<- prometheus.Metric) { s.lock.Lock() defer s.lock.Unlock() s.scrapeBegin = time.Now() // This ts is used for cache expiration check // check server conn, gathering fact if s.err = s.beforeScrape(s); s.err != nil { logDebugf("fail establishing connection to %s: %s", s.Name(), s.err.Error()) goto final } // fact change (including first time) will incur a plan procedure if !s.Planned { s.Plan() } // First pass: execute all queries with Fatal flag if err := s.collectFatalQueries(ch); err != nil { s.err = err goto final } // Second pass: execute remaining non-Fatal queries s.collectNonFatalQueries(ch) final: s.scrapeDone = time.Now() // This ts is used for cache expiration check s.totalTime += s.scrapeDone.Sub(s.scrapeBegin).Seconds() s.totalCount++ if s.err != nil { s.UP = false s.errorCount++ logErrorf("fail scraping server [%s]: %s", s.Name(), s.err.Error()) } else { s.UP = true logDebugf("server [%s] scraped in %v", s.Name(), s.scrapeDone.Sub(s.scrapeBegin).Seconds()) } } // collectFatalQueries executes all queries with Fatal flag and returns on first error func (s *Server) collectFatalQueries(ch chan<- prometheus.Metric) error { for _, query := range s.Collectors { if !query.Fatal { continue } if err := s.executeQuery(query, ch); err != nil { logErrorf("query [%s] error: %s", query.Name, err) return err } } return nil } // collectNonFatalQueries executes all non-Fatal queries and logs errors without stopping func (s *Server) collectNonFatalQueries(ch chan<- prometheus.Metric) { for _, query := range s.Collectors { if query.Fatal { continue } if err := s.executeQuery(query, ch); err != nil { logWarnf("query [%s] error skipped: %s", query.Name, err) } } } // executeQuery runs a single query and updates its metrics func (s *Server) executeQuery(query *Collector, ch chan<- prometheus.Metric) error { query.Collect(ch) s.queryCacheTTL[query.Name] = query.cacheTTL() s.queryScrapeTotalCount[query.Name]++ s.queryScrapeMetricCount[query.Name] = float64(query.ResultSize()) s.queryScrapeDuration[query.Name] = query.scrapeDuration.Seconds() if query.Error() != nil { s.queryScrapeErrorCount[query.Name]++ return query.Error() } if query.CacheHit() { s.queryScrapeHitCount[query.Name]++ } // Update predicate skip count if applicable if len(query.PredicateQueries) > 0 { skipped, _ := query.PredicateSkip() if skipped { s.queryScrapePredicateSkipCount[query.Name]++ } } return nil } // HasTag tells whether this server have specific tag func (s *Server) HasTag(tag string) bool { for _, t := range s.Tags { if t == tag { return true } } return false } // Duration returns last scrape duration in float64 seconds func (s *Server) Duration() float64 { s.lock.RLock() defer s.lock.RUnlock() sec := s.scrapeDone.Sub(s.scrapeBegin).Seconds() return sec } // Uptime returns servers's uptime func (s *Server) Uptime() float64 { return time.Since(s.serverInit).Seconds() } /* ================ Server Creation ================ */ // NewServer will check dsn, but not trying to connect func NewServer(dsn string, opts ...ServerOpt) *Server { s := &Server{dsn: dsn} for _, opt := range opts { opt(s) } s.Database = ParseDatname(dsn) if s.Database != "pgbouncer" { s.PgbouncerMode = false s.beforeScrape = PostgresPrecheck } else { logInfof("datname pgbouncer detected, enabling pgbouncer mode") s.PgbouncerMode = true s.beforeScrape = PgbouncerPrecheck } s.MaxConn = 1 s.Databases = make(map[string]bool, 1) s.serverInit = time.Now() return s } // ServerOpt configures Server type ServerOpt func(*Server) // WithConstLabel copy constant label map to server func WithConstLabel(labels prometheus.Labels) ServerOpt { return func(s *Server) { if labels == nil { s.labels = nil } else { s.labels = make(prometheus.Labels, len(labels)) for k, v := range labels { s.labels[k] = v } } } } // WithCachePolicy will pass cache option to server func WithCachePolicy(disableCache bool) ServerOpt { return func(s *Server) { s.DisableCache = disableCache } } // WithQueries set server's default query set func WithQueries(queries map[string]*Query) ServerOpt { return func(s *Server) { s.queries = queries } } // WithServerTags will mark server only execute query without cluster tag func WithServerTags(tags []string) ServerOpt { return func(s *Server) { s.Tags = tags } } // WithServerConnectTimeout will set a connect timeout for server precheck queries // otherwise, a default value 100ms will be used. // Increase this value if you are monitoring a remote (cross-DC, cross-AZ) instance func WithServerConnectTimeout(timeout int) ServerOpt { return func(s *Server) { s.ConnectTimeout = timeout } } pg_exporter-1.4.1/exporter/server_exporter_test.go000066400000000000000000000153451523237665500225750ustar00rootroot00000000000000package exporter import ( "strings" "testing" "time" "github.com/prometheus/client_golang/prometheus" ) func makeGaugeQuery(name string, priority int, tags ...string) *Query { return &Query{ Name: name, Branch: name, SQL: "SELECT 'db' AS datname, 1 AS value", Tags: tags, Priority: priority, Columns: map[string]*Column{ "datname": {Name: "datname", Usage: LABEL, Desc: "database"}, "value": {Name: "value", Usage: GAUGE, Desc: "value"}, }, ColumnNames: []string{"datname", "value"}, LabelNames: []string{"datname"}, MetricNames: []string{"value"}, } } func TestParseSemver(t *testing.T) { if got := ParseSemver("1.2.3"); got != 10203 { t.Fatalf("ParseSemver 1.2.3 = %d, want 10203", got) } if got := ParseSemver("PgBouncer 1.22.1"); got != 12201 { t.Fatalf("ParseSemver pgbouncer string = %d, want 12201", got) } if got := ParseSemver("invalid"); got != 0 { t.Fatalf("ParseSemver invalid = %d, want 0", got) } } func TestNewServerAndBasics(t *testing.T) { labels := prometheus.Labels{"k": "v"} q := map[string]*Query{"q": makeGaugeQuery("q", 1)} s := NewServer( "postgresql://user:pass@localhost:5432/postgres?sslmode=disable", WithConstLabel(labels), WithCachePolicy(true), WithQueries(q), WithServerTags([]string{"tag1"}), WithServerConnectTimeout(250), ) if s.PgbouncerMode { t.Fatal("postgres database should not trigger pgbouncer mode") } if !s.DisableCache { t.Fatal("WithCachePolicy(true) not applied") } if s.ConnectTimeout != 250 { t.Fatalf("ConnectTimeout = %d, want 250", s.ConnectTimeout) } if !s.HasTag("tag1") { t.Fatal("WithServerTags not applied") } if got := s.GetConnectTimeout(); got != 250*time.Millisecond { t.Fatalf("GetConnectTimeout = %v, want 250ms", got) } if got := s.Name(); got != "postgres" { t.Fatalf("Name = %s, want postgres", got) } s.Database = "" if got := s.Name(); !strings.Contains(got, "postgresql://") { t.Fatalf("Name with empty database should return redacted dsn, got: %s", got) } s2 := NewServer("postgresql://user:pass@localhost:5432/pgbouncer") if !s2.PgbouncerMode { t.Fatal("pgbouncer database should trigger pgbouncer mode") } } func TestServerCompatible(t *testing.T) { s := NewServer("postgresql://user:pass@localhost:5432/postgres") s.Version = 160000 s.Recovery = false s.Database = "postgres" s.Username = "monitor" s.Extensions = map[string]bool{"pg_stat_statements": true} s.Namespaces = map[string]bool{"public": true} s.Tags = []string{"foo"} tests := []struct { name string q *Query ok bool }{ {name: "skip", q: &Query{Name: "q", Skip: true}, ok: false}, {name: "pgbouncer tag mismatch", q: &Query{Name: "q", Tags: []string{"pgbouncer"}}, ok: false}, {name: "min version", q: &Query{Name: "q", MinVersion: 170000}, ok: false}, {name: "max version", q: &Query{Name: "q", MaxVersion: 160000}, ok: false}, {name: "extension exists", q: &Query{Name: "q", Tags: []string{"extension:pg_stat_statements"}}, ok: true}, {name: "extension missing", q: &Query{Name: "q", Tags: []string{"extension:missing"}}, ok: false}, {name: "schema exists", q: &Query{Name: "q", Tags: []string{"schema:public"}}, ok: true}, {name: "schema missing", q: &Query{Name: "q", Tags: []string{"schema:private"}}, ok: false}, {name: "dbname mismatch", q: &Query{Name: "q", Tags: []string{"dbname:other"}}, ok: false}, {name: "username match", q: &Query{Name: "q", Tags: []string{"username:monitor"}}, ok: true}, {name: "username mismatch", q: &Query{Name: "q", Tags: []string{"username:other"}}, ok: false}, {name: "forbidden not tag", q: &Query{Name: "q", Tags: []string{"not:foo"}}, ok: false}, {name: "server tag match", q: &Query{Name: "q", Tags: []string{"foo"}}, ok: true}, } for _, tt := range tests { got, _ := s.Compatible(tt.q) if got != tt.ok { t.Fatalf("%s compatible = %v, want %v", tt.name, got, tt.ok) } } s.Forked = true if ok, _ := s.Compatible(&Query{Name: "q", Tags: []string{"cluster"}}); ok { t.Fatal("cluster query should not run on forked server") } s.Forked = false s.Recovery = true if ok, _ := s.Compatible(&Query{Name: "q", Tags: []string{"primary"}}); ok { t.Fatal("primary query should not run on recovery server") } if ok, _ := s.Compatible(&Query{Name: "q", Tags: []string{"replica"}}); !ok { t.Fatal("replica query should run on recovery server") } } func TestPlanResetAndCollectCached(t *testing.T) { s := NewServer("postgresql://user:pass@localhost:5432/postgres") s.Version = 160000 s.Database = "postgres" s.Username = "monitor" s.Namespaces = map[string]bool{"public": true} s.Extensions = map[string]bool{} q1 := makeGaugeQuery("q1", 20) q2 := makeGaugeQuery("q2", 10) s.queries = map[string]*Query{"q1": q1, "q2": q2} s.Plan() if !s.Planned { t.Fatal("Plan should mark server planned") } if len(s.Collectors) != 2 { t.Fatalf("collector count = %d, want 2", len(s.Collectors)) } if s.Collectors[0].Name != "q2" || s.Collectors[1].Name != "q1" { t.Fatalf("collectors should be sorted by priority, got %s then %s", s.Collectors[0].Name, s.Collectors[1].Name) } // Build one cached metric for q2, so Collect path does not need DB. c := s.Collectors[0] metric := prometheus.MustNewConstMetric(c.descriptors["value"], prometheus.GaugeValue, 1, "db") c.result = []prometheus.Metric{metric} c.TTL = 3600 c.lastScrape = time.Now() c.err = nil s.beforeScrape = func(s *Server) error { s.UP = true return nil } s.Collectors = []*Collector{c} s.ResetStats() s.Planned = true ch := make(chan prometheus.Metric, 10) s.Collect(ch) if !s.UP { t.Fatal("Collect should keep server UP for successful cached query") } if s.totalCount != 1 { t.Fatalf("totalCount = %v, want 1", s.totalCount) } if s.queryScrapeTotalCount[c.Name] != 1 { t.Fatalf("queryScrapeTotalCount = %v, want 1", s.queryScrapeTotalCount[c.Name]) } if s.queryScrapeMetricCount[c.Name] != 1 { t.Fatalf("queryScrapeMetricCount = %v, want 1", s.queryScrapeMetricCount[c.Name]) } if s.queryScrapeHitCount[c.Name] != 1 { t.Fatalf("queryScrapeHitCount = %v, want 1", s.queryScrapeHitCount[c.Name]) } } func TestExporterServerLifecycleHelpers(t *testing.T) { e := &Exporter{ dsn: "postgresql://user:pass@localhost:5432/postgres?sslmode=disable", servers: map[string]*Server{}, queries: map[string]*Query{"q": makeGaugeQuery("q", 1)}, } e.CreateServer("db1") if len(e.servers) != 1 { t.Fatalf("CreateServer count = %d, want 1", len(e.servers)) } snapshot := e.IterateServer() if len(snapshot) != 1 || snapshot[0] == nil { t.Fatalf("IterateServer snapshot invalid: %#v", snapshot) } if !snapshot[0].Forked { t.Fatal("CreateServer should mark new server as Forked") } e.RemoveServer("db1") if len(e.servers) != 0 { t.Fatalf("RemoveServer count = %d, want 0", len(e.servers)) } } pg_exporter-1.4.1/exporter/testmain_test.go000066400000000000000000000002161523237665500211520ustar00rootroot00000000000000package exporter import ( "os" "testing" ) func TestMain(m *testing.M) { Logger = configureLogger("error", "logfmt") os.Exit(m.Run()) } pg_exporter-1.4.1/exporter/utils.go000066400000000000000000000142251523237665500174340ustar00rootroot00000000000000package exporter import ( "fmt" "log/slog" "math" "os" "strconv" "strings" "time" "github.com/prometheus/client_golang/prometheus" ) /* ================ Logger ================ */ func configureLogger(levelStr, formatStr string) *slog.Logger { var level slog.Level switch strings.ToLower(levelStr) { case "debug": level = slog.LevelDebug case "info": level = slog.LevelInfo case "warn": level = slog.LevelWarn case "error": level = slog.LevelError default: level = slog.LevelInfo // fallback to default info level } opts := &slog.HandlerOptions{ Level: level, } var handler slog.Handler switch strings.ToLower(formatStr) { case "json": handler = slog.NewJSONHandler(os.Stderr, opts) case "logfmt", "": handler = slog.NewTextHandler(os.Stderr, opts) default: // Be resilient to misconfiguration: fall back to logfmt. handler = slog.NewTextHandler(os.Stderr, opts) } return slog.New(handler) } func loggerOrDefault() *slog.Logger { if Logger != nil { return Logger } return slog.Default() } // logDebugf will log debug message func logDebugf(format string, v ...interface{}) { loggerOrDefault().Debug(fmt.Sprintf(format, v...)) } // logInfof will log info message func logInfof(format string, v ...interface{}) { loggerOrDefault().Info(fmt.Sprintf(format, v...)) } // logWarnf will log warning message func logWarnf(format string, v ...interface{}) { loggerOrDefault().Warn(fmt.Sprintf(format, v...)) } // logErrorf will log error message func logErrorf(format string, v ...interface{}) { loggerOrDefault().Error(fmt.Sprintf(format, v...)) } // logError will print error message directly func logError(msg string) { loggerOrDefault().Error(msg) } // logFatalf will log error message func logFatalf(format string, v ...interface{}) { loggerOrDefault().Error(fmt.Sprintf(format, v...)) os.Exit(1) } /* ================ Auxiliaries ================ */ // castFloat64 will cast datum into float64 with Column scale & default value. // Column.Scale/Column.Default are parsed when loading config, so this is hot-path safe. func castFloat64(t interface{}, c *Column) float64 { scale := 1.0 if c != nil && c.hasScale { scale = c.scaleFactor } switch v := t.(type) { case int64: return float64(v) * scale case float64: return v * scale case time.Time: return float64(v.Unix()) case []byte: strV := string(v) result, err := strconv.ParseFloat(strV, 64) if err != nil { logWarnf("fail casting []byte to float64: %v", t) return math.NaN() } return result * scale case string: result, err := strconv.ParseFloat(v, 64) if err != nil { logWarnf("fail casting string to float64: %v", t) return math.NaN() } return result * scale case bool: if v { return 1.0 } return 0.0 case nil: if c != nil && c.hasDefault { return c.defaultValue * scale } return math.NaN() default: logWarnf("fail casting unknown to float64: %v", t) return math.NaN() } } // castHistogramFloat64 converts one raw SQL histogram observation. A NULL is // absent unless the column explicitly defines a default. Histogram samples are // required to be finite because one invalid observation invalidates the whole // snapshot rather than publishing a partial distribution. func castHistogramFloat64(t interface{}, c *Column) (value float64, present bool, err error) { if t == nil && (c == nil || !c.hasDefault) { return 0, false, nil } scale := 1.0 if c != nil && c.hasScale { scale = c.scaleFactor } if t == nil { t = c.defaultValue } // scale semantics must mirror castFloat64: epoch timestamps and booleans // are never scaled, so a column yields the same value under any usage switch v := t.(type) { case int64: value = float64(v) * scale case float64: value = v * scale case time.Time: value = float64(v.Unix()) case []byte: value, err = strconv.ParseFloat(string(v), 64) value *= scale case string: value, err = strconv.ParseFloat(v, 64) value *= scale case bool: if v { value = 1 } default: err = fmt.Errorf("cannot cast %T to float64", t) } if err != nil { return 0, false, err } if math.IsNaN(value) || math.IsInf(value, 0) { return 0, false, fmt.Errorf("non-finite observation %v", value) } return value, true, nil } // encodeLabelTuple returns an unambiguous map key for an ordered label tuple. // Length-prefixing avoids collisions such as ["a", "bc"] and ["ab", "c"]. func encodeLabelTuple(labels []string) string { var b strings.Builder for _, label := range labels { b.WriteString(strconv.Itoa(len(label))) b.WriteByte(':') b.WriteString(label) } return b.String() } // castString will force interface{} into string func castString(t interface{}) string { switch v := t.(type) { case int64: return fmt.Sprintf("%v", v) case float64: return fmt.Sprintf("%v", v) case time.Time: return fmt.Sprintf("%v", v.Unix()) case nil: return "" case []byte: // Try and convert to string return string(v) case string: return v case bool: if v { return "true" } return "false" default: logWarnf("fail casting unknown to string: %v", t) return "" } } // parseConstLabels turn param string into prometheus.Labels func parseConstLabels(s string) prometheus.Labels { labels := make(prometheus.Labels) s = strings.TrimSpace(s) if len(s) == 0 { return nil } parts := strings.Split(s, ",") for _, p := range parts { keyValue := strings.SplitN(strings.TrimSpace(p), "=", 2) if len(keyValue) != 2 { logErrorf(`malformed labels format %q, should be "key=value"`, p) continue } key := strings.TrimSpace(keyValue[0]) value := strings.TrimSpace(keyValue[1]) if key == "" || value == "" { continue } if err := validatePromLabelName(key); err != nil { logWarnf("skip invalid const label name %q: %v", key, err) continue } labels[key] = value } if len(labels) == 0 { return nil } return labels } // parseCSV will turn a comma separated string into a []string func parseCSV(s string) (tags []string) { s = strings.TrimSpace(s) if len(s) == 0 { return nil } parts := strings.Split(s, ",") for _, p := range parts { if tag := strings.TrimSpace(p); len(tag) > 0 { tags = append(tags, tag) } } if len(tags) == 0 { return nil } return } pg_exporter-1.4.1/exporter/utils_test.go000066400000000000000000000102251523237665500204670ustar00rootroot00000000000000package exporter import ( "math" "reflect" "testing" "time" ) func TestParseCSV(t *testing.T) { if got := parseCSV(""); got != nil { t.Fatalf("parseCSV empty = %v, want nil", got) } got := parseCSV(" a, b,, c , ") want := []string{"a", "b", "c"} if !reflect.DeepEqual(got, want) { t.Fatalf("parseCSV result = %#v, want %#v", got, want) } } func TestParseConstLabels(t *testing.T) { if got := parseConstLabels(""); got != nil { t.Fatalf("parseConstLabels empty = %v, want nil", got) } labels := parseConstLabels("env=prod,region=us-east-1") if labels["env"] != "prod" || labels["region"] != "us-east-1" { t.Fatalf("parseConstLabels valid result = %v", labels) } labels = parseConstLabels("token=a=b=c") if labels["token"] != "a=b=c" { t.Fatalf("parseConstLabels should preserve '=' in value, got %q", labels["token"]) } labels = parseConstLabels("bad,noeq=,=noval,ok=1") if len(labels) != 1 || labels["ok"] != "1" { t.Fatalf("parseConstLabels malformed handling = %v", labels) } } func TestCastFloat64(t *testing.T) { now := time.Unix(1700000000, 0) withScale := func(scale, def string) *Column { c := &Column{Scale: scale, Default: def} if err := c.parseNumbers(); err != nil { t.Fatalf("parseNumbers failed for scale=%q default=%q: %v", scale, def, err) } return c } if got := castFloat64(int64(3), withScale("2", "")); got != 6 { t.Fatalf("int64 scale cast = %v, want 6", got) } if got := castFloat64(float64(1.5), withScale("2", "")); got != 3 { t.Fatalf("float64 scale cast = %v, want 3", got) } if got := castFloat64(now, nil); got != float64(now.Unix()) { t.Fatalf("time cast = %v, want %v", got, now.Unix()) } if got := castFloat64([]byte("3.25"), withScale("10", "")); got != 32.5 { t.Fatalf("[]byte cast = %v, want 32.5", got) } if got := castFloat64("2.5", withScale("4", "")); got != 10 { t.Fatalf("string cast = %v, want 10", got) } if got := castFloat64(true, nil); got != 1 { t.Fatalf("bool true cast = %v, want 1", got) } if got := castFloat64(false, nil); got != 0 { t.Fatalf("bool false cast = %v, want 0", got) } if got := castFloat64(nil, withScale("", "2.5")); got != 2.5 { t.Fatalf("nil default cast = %v, want 2.5", got) } if got := castFloat64(nil, withScale("10", "2.5")); got != 25 { t.Fatalf("nil default cast should apply scale: %v, want 25", got) } if got := castFloat64("abc", nil); !math.IsNaN(got) { t.Fatalf("invalid string cast = %v, want NaN", got) } if got := castFloat64(struct{}{}, nil); !math.IsNaN(got) { t.Fatalf("unknown type cast = %v, want NaN", got) } // Invalid numeric options should be rejected at parse time. if err := (&Column{Scale: "bad"}).parseNumbers(); err == nil { t.Fatal("parseNumbers should fail for invalid scale") } if err := (&Column{Default: "bad"}).parseNumbers(); err == nil { t.Fatal("parseNumbers should fail for invalid default") } } func TestCastString(t *testing.T) { now := time.Unix(1700000000, 0) if got := castString(int64(3)); got != "3" { t.Fatalf("int64 cast = %q, want 3", got) } if got := castString(float64(1.5)); got != "1.5" { t.Fatalf("float64 cast = %q, want 1.5", got) } if got := castString(now); got != "1700000000" { t.Fatalf("time cast = %q, want 1700000000", got) } if got := castString([]byte("abc")); got != "abc" { t.Fatalf("[]byte cast = %q, want abc", got) } if got := castString(true); got != "true" { t.Fatalf("bool true cast = %q, want true", got) } if got := castString(nil); got != "" { t.Fatalf("nil cast = %q, want empty", got) } } func TestConfigureLogger(t *testing.T) { if l := configureLogger("debug", "json"); l == nil { t.Fatal("configureLogger returned nil for valid json format") } if l := configureLogger("bad-level", "logfmt"); l == nil { t.Fatal("configureLogger returned nil for fallback level") } if l := configureLogger("info", "unknown-format"); l == nil { t.Fatal("configureLogger returned nil for unknown format fallback") } } func TestLogHelpersWithNilLogger(t *testing.T) { origin := Logger Logger = nil t.Cleanup(func() { Logger = origin }) logDebugf("debug %d", 1) logInfof("info %d", 1) logWarnf("warn %d", 1) logErrorf("error %d", 1) logError("plain error") } pg_exporter-1.4.1/exporter/validate_labels.go000066400000000000000000000034071523237665500214070ustar00rootroot00000000000000package exporter import ( "fmt" "sort" "github.com/prometheus/client_golang/prometheus" ) // validateConstLabelConflicts rejects constant label keys that would cause a // Prometheus panic due to duplicate label names between const and variable labels. // // This can happen when a user passes `--label key=value` (or PG_EXPORTER_LABEL) // where `key` equals one of a query's metric label names (after rename). // // When intro metrics are enabled, it also rejects keys that collide with the // exporter internal dynamic metric labels (currently: datname, query). func validateConstLabelConflicts(constLabels prometheus.Labels, queries map[string]*Query, disableIntro bool) error { if len(constLabels) == 0 { return nil } // Exporter internal dynamic metrics use these variable labels. if !disableIntro { for _, reserved := range []string{"datname", "query"} { if _, exists := constLabels[reserved]; exists { return fmt.Errorf("const label %q conflicts with built-in exporter metric label %q", reserved, reserved) } } } if len(queries) == 0 { return nil } // Stable iteration order for deterministic error messages. branches := make([]string, 0, len(queries)) for b := range queries { branches = append(branches, b) } sort.Strings(branches) for _, branch := range branches { q := queries[branch] if q == nil { continue } if _, exists := constLabels["le"]; exists && q.HasHistogram() { return fmt.Errorf("const label %q conflicts with query %q (name=%q) generated Histogram bucket label %q", "le", branch, q.Name, "le") } for _, lbl := range q.LabelList() { if _, exists := constLabels[lbl]; exists { return fmt.Errorf("const label %q conflicts with query %q (name=%q) label %q", lbl, branch, q.Name, lbl) } } } return nil } pg_exporter-1.4.1/exporter/validate_labels_test.go000066400000000000000000000031111523237665500224360ustar00rootroot00000000000000package exporter import ( "strings" "testing" ) func TestValidateConstLabelConflicts_QueryLabelOverlap(t *testing.T) { cfg := ` q1: query: SELECT 1 AS value, 'x' AS datname metrics: - datname: usage: LABEL rename: db description: database - value: usage: GAUGE description: value ` queries, err := ParseConfig([]byte(cfg)) if err != nil { t.Fatalf("ParseConfig failed: %v", err) } labels := parseConstLabels("db=foo") if err := validateConstLabelConflicts(labels, queries, false); err == nil { t.Fatal("expected const label conflict error, got nil") } } func TestValidateConstLabelConflicts_InternalReservedLabels(t *testing.T) { labels := parseConstLabels("datname=foo") if err := validateConstLabelConflicts(labels, nil, false); err == nil { t.Fatal("expected reserved label conflict with intro metrics enabled, got nil") } // When intro metrics are disabled, internal dynamic series are not emitted. if err := validateConstLabelConflicts(labels, nil, true); err != nil { t.Fatalf("expected no error with intro metrics disabled, got %v", err) } } func TestNewExporterRejectsConstLabelConflict(t *testing.T) { cfg := ` q1: query: SELECT 1 AS value, 'x' AS datname metrics: - datname: usage: LABEL rename: db - value: usage: GAUGE ` _, err := NewExporter( "postgresql://u:p@localhost:5432/postgres?sslmode=disable", WithConfigReader(strings.NewReader(cfg)), WithConstLabels("db=foo"), ) if err == nil { t.Fatal("expected NewExporter to fail on const label conflict, got nil") } } pg_exporter-1.4.1/go.mod000066400000000000000000000026571523237665500152110ustar00rootroot00000000000000module pg_exporter go 1.26.5 require ( github.com/alecthomas/kingpin/v2 v2.4.0 github.com/lib/pq v1.12.3 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.70.0 github.com/prometheus/exporter-toolkit v0.17.1 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/klauspost/compress v1.18.6 // indirect github.com/mdlayher/socket v0.6.1 // indirect github.com/mdlayher/vsock v1.3.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/prometheus/procfs v0.21.1 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.40.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) pg_exporter-1.4.1/go.sum000066400000000000000000000163631523237665500152350ustar00rootroot00000000000000github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= 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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A= github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg= github.com/mdlayher/vsock v1.3.0 h1:bqQfZ1OznI03y6YiXp2sze05RVdzLn/zsfjnjd4+ivI= github.com/mdlayher/vsock v1.3.0/go.mod h1:WsuksavOvwCnV5UqGHUkvAvCy+Dqy81y4goKQTzxxNY= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/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.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI= github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= github.com/prometheus/exporter-toolkit v0.17.1 h1:psKN4wM7shBL/BxZkDHgm6YZJ3fAVG36+r86An/+7q0= github.com/prometheus/exporter-toolkit v0.17.1/go.mod h1:dabwPJvxsC5+tsp2iolQrqBWZh+QlISKlYRpj9Hh5xk= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= pg_exporter-1.4.1/hugo.yaml000066400000000000000000000070211523237665500157170ustar00rootroot00000000000000baseURL: 'https://exp.pgsty.com/' languageCode: 'en' title: 'PG Exporter - Advanced PostgreSQL & pgBouncer Metrics Exporter' enableRobotsTXT: true # Parse Git commit enableGitInfo: true # enableEmoji: false hasCJKLanguage: true services: googleAnalytics: ID: G-Y7HMTRQ6P7 contentDir: docs outputs: home: [HTML] page: [HTML] section: [HTML, RSS] module: imports: - path: github.com/imfing/hextra defaultContentLanguage: en languages: en: languageName: English languageCode: en weight: 1 title: PG Exporter menu: main: - name: Pigsty weight: 1 url: "https://pigsty.io" - name: GitHub weight: 2 url: "https://github.com/pgsty/pg_exporter" params: icon: github - name: Search weight: 3 params: type: search sidebar: - identifier: more name: Links params: type: separator weight: 6 - identifier: pigsty name: "Pigsty ↗" url: "https://pigsty.io" weight: 7 - identifier: pig-cli name: "PIG CLI ↗" url: "https://pigsty.io/docs/pig" weight: 8 - identifier: pgext name: "PG Extensions ↗" url: "https://pgext.cloud" weight: 10 - identifier: github-repo name: "GitHub repo ↗" url: "https://github.com/pgsty/pg_exporter" weight: 11 - identifier: demo name: "Public Demo ↗" url: "https://demo.pigsty.io" weight: 12 - identifier: vonng name: "Author: Vonng ↗" url: "https://vonng.com/en/" weight: 13 markup: highlight: noClasses: false goldmark: renderer: unsafe: true extensions: passthrough: delimiters: block: [['\[', '\]'], ['$$', '$$']] inline: [['\(', '\)']] enable: true params: description: PG Exporter is an advanced PostgreSQL and pgBouncer metrics exporter for Prometheus, providing 600+ metrics with declarative YAML configuration. sidebar: autoCollapse: false navbar: displayTitle: true displayLogo: true logo: path: /logo.png dark: /logo.png width: wide page: # full (100%), wide (90rem), normal (80rem) width: full theme: # light | dark | system default: system displayToggle: true footer: enable: false displayCopyright: true displayPoweredBy: true width: normal # Display the last modification date displayUpdatedDate: true dateFormat: "2006-01-02" # Search # flexsearch is enabled by default search: enable: true type: flexsearch flexsearch: # index page by: content | summary | heading | title index: content # full | forward | reverse | strict # https://github.com/nextapps-de/flexsearch/#tokenizer-prefix-search tokenize: forward editURL: enable: true base: "https://github.com/pgsty/pg_exporter/edit/main/content" toc: displayTags: true highlight: copy: enable: true display: hover comments: enable: true type: giscus # https://giscus.app/ giscus: repo: "pgsty/pg_exporter" repoId: "MDEwOlJlcG9zaXRvcnkyMjYyNzkxOTc" category: "Announcements" categoryId: "DIC_kwDODXy_Hc4Ct5Xv" mapping: pathname strict: 0 reactionsEnabled: 1 emitMetadata: 0 inputPosition: bottom lang: en theme: transparent_dark pg_exporter-1.4.1/legacy/000077500000000000000000000000001523237665500153355ustar00rootroot00000000000000pg_exporter-1.4.1/legacy/README.md000066400000000000000000000016461523237665500166230ustar00rootroot00000000000000# Legacy Config Bundle (for PG 9.1 - 9.6) This directory contains the **legacy pg_exporter config bundle** for **PostgreSQL 9.1 - 9.6** (EOL). - `pg_exporter.yml`: merged legacy config (ready to use) - `config/`: separated collector definitions (source of truth) ## Generate / Update From the repository root: ```bash make conf9 ``` ## Usage Use the merged legacy config file: ```bash PG_EXPORTER_CONFIG=legacy/pg_exporter.yml \ PG_EXPORTER_URL='postgres://user:pass@host:5432/postgres' \ pg_exporter --auto-discovery --exclude-database=template0,template1 ``` Or load separated collectors directly (directory mode): ```bash PG_EXPORTER_CONFIG=legacy/config \ PG_EXPORTER_URL='postgres://user:pass@host:5432/postgres' \ pg_exporter --auto-discovery --exclude-database=template0,template1 ``` ## Notes - PostgreSQL **9.0 is not supported**. - For PostgreSQL **10+**, use the default config in repo root: `pg_exporter.yml`. pg_exporter-1.4.1/legacy/config/000077500000000000000000000000001523237665500166025ustar00rootroot00000000000000pg_exporter-1.4.1/legacy/config/0000-doc.yml000066400000000000000000000044341523237665500204540ustar00rootroot00000000000000#==============================================================# # Desc : pg_exporter metrics collector definition (Legacy) # Ver : PostgreSQL 9.1 ~ 9.6 and pgbouncer 1.9~1.25+ # Ctime : 2019-12-09 # Mtime : 2026-02-07 # Homepage : https://pigsty.io # Author : Ruohang Feng (rh@vonng.com) # License : Apache-2.0 @ https://github.com/pgsty/pg_exporter # Copyright : 2018-2026 Ruohang Feng / Vonng (rh@vonng.com) #==============================================================# #==============================================================# # 1. Config File #==============================================================# # The configuration file for pg_exporter is a YAML file. # Default configurations are retrieved via following precedence: # 1. command line args: --config= # 2. environment variables: PG_EXPORTER_CONFIG= # 3. pg_exporter.yml (Current directory) # 4. /etc/pg_exporter.yml (config file) # 5. /etc/pg_exporter (config dir) #==============================================================# # 2. Config Format #==============================================================# # pg_exporter config could be a single YAML file, or a directory containing a series of separated YAML files. # Each YAML config file consists of one or more metrics Collector definition, which are top-level objects. # If a directory is provided, all YAML in that directory will be merged in alphabetic order. #==============================================================# # 3. Version Compatibility #==============================================================# # Each collector has two optional version compatibility parameters: `min_version` and `max_version`. # These two parameters specify the version compatibility of the collector. If target postgres/pgbouncer's # version is less than `min_version`, or higher than `max_version`, the collector will not be installed. # # These two parameters are using PostgreSQL server version number format, which is a 6-digit integer # format as :. # # For example: # - 90100 stands for 9.1 # - 90600 stands for 9.6 # - 100000 stands for 10.0 # # Version compatibility range is left-inclusive right-exclusive: [min, max) pg_exporter-1.4.1/legacy/config/0110-pg.yml000066400000000000000000000157221523237665500203210ustar00rootroot00000000000000#==============================================================# # 0110 pg #==============================================================# pg_primary_only: name: pg desc: PostgreSQL basic information (on primary) query: |- SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, extract(EPOCH FROM pg_postmaster_start_time()) AS boot_time, (('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, (('x' || lpad(split_part(pg_current_xlog_insert_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_current_xlog_insert_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS insert_lsn, (('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS write_lsn, (('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, NULL::BIGINT AS receive_lsn, NULL::BIGINT AS replay_lsn, extract(EPOCH FROM pg_conf_load_time()) AS reload_time, extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, NULL::FLOAT AS last_replay_time, 0::FLOAT AS lag, pg_is_in_recovery() AS is_in_recovery, FALSE AS is_wal_replay_paused; tags: [ cluster, primary ] ttl: 1 min_version: 90100 max_version: 100000 fatal: true skip: false metrics: - timestamp: { usage: GAUGE ,description: "current database timestamp in unix epoch" } - uptime: { usage: GAUGE ,description: "seconds since postmaster start" } - boot_time: { usage: GAUGE ,description: "postmaster boot timestamp in unix epoch" } - lsn: { usage: COUNTER ,description: "log sequence number, current write location" } - insert_lsn: { usage: COUNTER ,description: "primary only, location of current wal inserting" } - write_lsn: { usage: COUNTER ,description: "primary only, location of current wal writing" } - flush_lsn: { usage: COUNTER ,description: "primary only, location of current wal syncing" } - receive_lsn: { usage: COUNTER ,description: "replica only, location of wal synced to disk" } - replay_lsn: { usage: COUNTER ,description: "replica only, location of wal applied" } - reload_time: { usage: GAUGE ,description: "time when configuration was last reloaded" } - conf_reload_time: { usage: GAUGE ,description: "seconds since last configuration reload" } - last_replay_time: { usage: GAUGE ,description: "time when last transaction been replayed" } - lag: { usage: GAUGE ,description: "replica only, replication lag in seconds" } - is_in_recovery: { usage: GAUGE ,description: "1 if in recovery mode" } - is_wal_replay_paused: { usage: GAUGE ,description: "1 if wal play is paused" } pg_replica_only: name: pg desc: PostgreSQL basic information (on replica, 9.1+) query: |- SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, extract(EPOCH FROM pg_postmaster_start_time()) AS boot_time, (('x' || lpad(split_part(pg_last_xlog_replay_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_last_xlog_replay_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, NULL::BIGINT AS insert_lsn, NULL::BIGINT AS write_lsn, NULL::BIGINT AS flush_lsn, (('x' || lpad(split_part(pg_last_xlog_receive_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_last_xlog_receive_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS receive_lsn, (('x' || lpad(split_part(pg_last_xlog_replay_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_last_xlog_replay_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS replay_lsn, extract(EPOCH FROM pg_conf_load_time()) AS reload_time, extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, extract(EPOCH FROM pg_last_xact_replay_timestamp()) AS last_replay_time, CASE WHEN pg_last_xlog_receive_location() = pg_last_xlog_replay_location() THEN 0 ELSE EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()) END AS lag, pg_is_in_recovery() AS is_in_recovery, pg_is_xlog_replay_paused() AS is_wal_replay_paused; tags: [ cluster, replica ] ttl: 1 min_version: 90100 max_version: 100000 fatal: true skip: false metrics: - timestamp: { usage: GAUGE ,description: "current database timestamp in unix epoch" } - uptime: { usage: GAUGE ,description: "seconds since postmaster start" } - boot_time: { usage: GAUGE ,description: "postmaster boot timestamp in unix epoch" } - lsn: { usage: COUNTER ,description: "log sequence number, current write location" } - insert_lsn: { usage: COUNTER ,description: "primary only, location of current wal inserting" } - write_lsn: { usage: COUNTER ,description: "primary only, location of current wal writing" } - flush_lsn: { usage: COUNTER ,description: "primary only, location of current wal syncing" } - receive_lsn: { usage: COUNTER ,description: "replica only, location of wal synced to disk" } - replay_lsn: { usage: COUNTER ,description: "replica only, location of wal applied" } - reload_time: { usage: GAUGE ,description: "time when configuration was last reloaded" } - conf_reload_time: { usage: GAUGE ,description: "seconds since last configuration reload" } - last_replay_time: { usage: GAUGE ,description: "time when last transaction been replayed" } - lag: { usage: GAUGE ,description: "replica only, replication lag in seconds" } - is_in_recovery: { usage: GAUGE ,description: "1 if in recovery mode" } - is_wal_replay_paused: { usage: GAUGE ,description: "1 if wal play is paused" } pg_exporter-1.4.1/legacy/config/0120-pg_meta.yml000066400000000000000000000121751523237665500213270ustar00rootroot00000000000000#==============================================================# # 0120 pg_meta #==============================================================# pg_meta_96: name: pg_meta desc: PostgreSQL meta info for pg 9.6 (with pg_control_system) query: | SELECT (SELECT system_identifier FROM pg_control_system()) AS cluster_id, coalesce((SELECT setting FROM pg_settings WHERE name = 'cluster_name'), 'N/A') AS cluster_name, (SELECT setting FROM pg_settings WHERE name = 'port') AS listen_port, (SELECT setting FROM pg_settings WHERE name = 'data_directory') AS data_dir, (SELECT setting FROM pg_settings WHERE name = 'config_file') AS conf_path, (SELECT setting FROM pg_settings WHERE name = 'hba_file') AS hba_path, (SELECT setting FROM pg_settings WHERE name = 'wal_level') AS wal_level, (SELECT setting FROM pg_settings WHERE name = 'server_encoding') AS encoding, (SELECT setting FROM pg_settings WHERE name = 'server_version') AS version, (SELECT setting FROM pg_settings WHERE name = 'server_version_num') AS ver_num, version() AS ver_str, (SELECT setting FROM pg_settings WHERE name = 'shared_preload_libraries') AS extensions, coalesce((SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'), 'N/A') AS primary_conninfo, 1 AS info; ttl: 10 min_version: 90600 tags: [ cluster ] metrics: - cluster_id: { usage: LABEL ,description: "cluster system identifier" } - cluster_name: { usage: LABEL ,description: "cluster name" } - listen_port: { usage: LABEL ,description: "listen port" } - data_dir: { usage: LABEL ,description: "path to data directory" } - conf_path: { usage: LABEL ,description: "path to postgresql.conf" } - hba_path: { usage: LABEL ,description: "path to pg_hba.conf" } - wal_level: { usage: LABEL ,description: "wal level" } - encoding: { usage: LABEL ,description: "server encoding" } - version: { usage: LABEL ,description: "server version in human-readable format" } - ver_num: { usage: LABEL ,description: "server version number in machine-readable format" } - ver_str: { usage: LABEL ,description: "complete version string" } - extensions: { usage: LABEL ,description: "server installed preload libraries" } - primary_conninfo: { usage: LABEL ,description: "connection string to upstream (do not set password here)" } - info: { usage: GAUGE ,description: "constant 1" } pg_meta_91: name: pg_meta desc: PostgreSQL meta info for pg 9.1 - 9.5 query: | SELECT 'N/A' AS cluster_id, coalesce((SELECT setting FROM pg_settings WHERE name = 'cluster_name'), 'N/A') AS cluster_name, (SELECT setting FROM pg_settings WHERE name = 'port') AS listen_port, (SELECT setting FROM pg_settings WHERE name = 'data_directory') AS data_dir, (SELECT setting FROM pg_settings WHERE name = 'config_file') AS conf_path, (SELECT setting FROM pg_settings WHERE name = 'hba_file') AS hba_path, (SELECT setting FROM pg_settings WHERE name = 'wal_level') AS wal_level, (SELECT setting FROM pg_settings WHERE name = 'server_encoding') AS encoding, (SELECT setting FROM pg_settings WHERE name = 'server_version') AS version, (SELECT setting FROM pg_settings WHERE name = 'server_version_num') AS ver_num, version() AS ver_str, (SELECT setting FROM pg_settings WHERE name = 'shared_preload_libraries') AS extensions, coalesce((SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'), 'N/A') AS primary_conninfo, 1 AS info; ttl: 10 min_version: 90100 max_version: 90600 tags: [ cluster ] metrics: - cluster_id: { usage: LABEL ,description: "cluster system identifier" } - cluster_name: { usage: LABEL ,description: "cluster name" } - listen_port: { usage: LABEL ,description: "listen port" } - data_dir: { usage: LABEL ,description: "path to data directory" } - conf_path: { usage: LABEL ,description: "path to postgresql.conf" } - hba_path: { usage: LABEL ,description: "path to pg_hba.conf" } - wal_level: { usage: LABEL ,description: "wal level" } - encoding: { usage: LABEL ,description: "server encoding" } - version: { usage: LABEL ,description: "server version in human-readable format" } - ver_num: { usage: LABEL ,description: "server version number in machine-readable format" } - ver_str: { usage: LABEL ,description: "complete version string" } - extensions: { usage: LABEL ,description: "server installed preload libraries" } - primary_conninfo: { usage: LABEL ,description: "connection string to upstream (do not set password here)" } - info: { usage: GAUGE ,description: "constant 1" } pg_exporter-1.4.1/legacy/config/0130-pg_setting.yml000066400000000000000000000227021523237665500220540ustar00rootroot00000000000000#==============================================================# # 0130 pg_setting #==============================================================# # Key PostgreSQL configuration parameters for PostgreSQL 9.1 - 9.6 # Use scalar subquery on pg_settings for "missing_ok" semantics (return NULL if not exist) pg_setting: name: pg_setting desc: PostgreSQL shared configuration parameters (legacy 9.1-9.6) query: | SELECT (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_connections, (SELECT setting::int FROM pg_settings WHERE name = 'max_prepared_transactions') AS max_prepared_transactions, (SELECT setting::int FROM pg_settings WHERE name = 'max_locks_per_transaction') AS max_locks_per_transaction, (SELECT setting::int FROM pg_settings WHERE name = 'max_worker_processes') AS max_worker_processes, (SELECT setting::int FROM pg_settings WHERE name = 'max_parallel_workers') AS max_parallel_workers, (SELECT setting::int FROM pg_settings WHERE name = 'max_parallel_workers_per_gather') AS max_parallel_workers_per_gather, (SELECT setting::int FROM pg_settings WHERE name = 'max_parallel_maintenance_workers') AS max_parallel_maintenance_workers, (SELECT setting::int FROM pg_settings WHERE name = 'max_replication_slots') AS max_replication_slots, (SELECT setting::int FROM pg_settings WHERE name = 'max_wal_senders') AS max_wal_senders, (SELECT setting::int FROM pg_settings WHERE name = 'block_size') AS block_size, (SELECT setting::int FROM pg_settings WHERE name = 'wal_block_size') AS wal_block_size, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'segment_size') AS segment_size, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'wal_segment_size') AS wal_segment_size, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'data_checksums') AS data_checksums, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'wal_log_hints') AS wal_log_hints, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'fsync') AS fsync, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'full_page_writes') AS full_page_writes, (SELECT CASE setting WHEN 'minimal' THEN 1 WHEN 'archive' THEN 2 WHEN 'hot_standby' THEN 3 ELSE 0 END FROM pg_settings WHERE name = 'wal_level') AS wal_level, (SELECT setting::int FROM pg_settings WHERE name = 'checkpoint_segments') AS checkpoint_segments, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'min_wal_size') AS min_wal_size, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'max_wal_size') AS max_wal_size, (SELECT setting::int FROM pg_settings WHERE name = 'wal_keep_segments') AS wal_keep_segments, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'shared_buffers') AS shared_buffers, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'work_mem') AS work_mem, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'maintenance_work_mem') AS maintenance_work_mem, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'effective_cache_size') AS effective_cache_size, (SELECT CASE setting WHEN 'off' THEN 0 WHEN 'on' THEN 1 WHEN 'always' THEN 2 ELSE -1 END FROM pg_settings WHERE name = 'archive_mode') AS archive_mode, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'autovacuum') AS autovacuum, (SELECT setting::int FROM pg_settings WHERE name = 'autovacuum_max_workers') AS autovacuum_max_workers, (SELECT setting::int FROM pg_settings WHERE name = 'checkpoint_timeout') AS checkpoint_timeout, (SELECT setting::float FROM pg_settings WHERE name = 'checkpoint_completion_target') AS checkpoint_completion_target, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'hot_standby') AS hot_standby, (SELECT CASE setting WHEN 'off' THEN 0 WHEN 'local' THEN 1 WHEN 'remote_write' THEN 2 WHEN 'on' THEN 3 WHEN 'remote_apply' THEN 4 ELSE -1 END FROM pg_settings WHERE name = 'synchronous_commit') AS synchronous_commit; ttl: 10 min_version: 90100 tags: [ cluster ] metrics: - max_connections: { usage: GAUGE ,description: "maximum number of concurrent connections to the database server" } - max_prepared_transactions: { usage: GAUGE ,description: "maximum number of transactions that can be in the prepared state simultaneously" } - max_locks_per_transaction: { usage: GAUGE ,description: "maximum number of locks per transaction" } - max_worker_processes: { usage: GAUGE ,description: "maximum number of background processes (9.4+)" } - max_parallel_workers: { usage: GAUGE ,description: "maximum number of parallel workers that can be active at one time (9.6+)" } - max_parallel_workers_per_gather: { usage: GAUGE ,description: "maximum number of parallel workers per Gather node (9.6+)" } - max_parallel_maintenance_workers: { usage: GAUGE ,description: "maximum number of parallel maintenance workers (NULL on 9.x)" } - max_replication_slots: { usage: GAUGE ,description: "maximum number of replication slots (9.4+)" } - max_wal_senders: { usage: GAUGE ,description: "maximum number of concurrent WAL sender connections" } - block_size: { usage: GAUGE ,description: "database block size in bytes (default 8192)" } - wal_block_size: { usage: GAUGE ,description: "WAL block size in bytes" } - segment_size: { usage: GAUGE ,description: "database file segment size in bytes" } - wal_segment_size: { usage: GAUGE ,description: "WAL segment size in bytes" } - data_checksums: { usage: GAUGE ,description: "data checksums enabled, 1=on 0=off (9.3+)" } - wal_log_hints: { usage: GAUGE ,description: "WAL log hints enabled, 1=on 0=off (9.4+)" } - fsync: { usage: GAUGE ,description: "fsync enabled (CRITICAL for data safety), 1=on 0=off" } - full_page_writes: { usage: GAUGE ,description: "full page writes enabled, 1=on 0=off" } - wal_level: { usage: GAUGE ,description: "WAL level, 1=minimal 2=archive 3=hot_standby" } - checkpoint_segments: { usage: GAUGE ,description: "number of checkpoint segments (pre-9.5)" } - min_wal_size: { usage: GAUGE ,description: "minimum WAL size in bytes (9.5+)" } - max_wal_size: { usage: GAUGE ,description: "maximum WAL size in bytes (9.5+)" } - wal_keep_segments: { usage: GAUGE ,description: "WAL segments kept for standby replication (pg_basebackup/streaming)" } - shared_buffers: { usage: GAUGE ,description: "shared buffer size in bytes" } - work_mem: { usage: GAUGE ,description: "work memory size in bytes" } - maintenance_work_mem: { usage: GAUGE ,description: "maintenance work memory size in bytes" } - effective_cache_size: { usage: GAUGE ,description: "planner's assumption about effective OS cache size in bytes" } - archive_mode: { usage: GAUGE ,description: "archive mode, 0=off 1=on 2=always" } - autovacuum: { usage: GAUGE ,description: "autovacuum enabled, 1=on 0=off" } - autovacuum_max_workers: { usage: GAUGE ,description: "maximum number of autovacuum worker processes" } - checkpoint_timeout: { usage: GAUGE ,description: "checkpoint timeout in seconds" } - checkpoint_completion_target: { usage: GAUGE ,description: "checkpoint completion target (0.0-1.0)" } - hot_standby: { usage: GAUGE ,description: "hot standby mode enabled, 1=on 0=off" } - synchronous_commit: { usage: GAUGE ,description: "synchronous commit level, 0=off 1=local 2=remote_write 3=on 4=remote_apply" } pg_exporter-1.4.1/legacy/config/0210-pg_repl.yml000066400000000000000000000410621523237665500213400ustar00rootroot00000000000000#==============================================================# # 0210 pg_repl #==============================================================# pg_repl_94: name: pg_repl desc: PostgreSQL replication stat metrics 9.4 - 9.6 (with backend_xmin) query: |- SELECT appname, usename, address, pid, client_port, state, sync_state, sync_priority, backend_xmin, lsn, lsn - sent_lsn AS sent_diff, lsn - write_lsn AS write_diff, lsn - flush_lsn AS flush_diff, lsn - replay_lsn AS replay_diff, sent_lsn, write_lsn, flush_lsn, replay_lsn, 0::FLOAT AS write_lag, 0::FLOAT AS flush_lag, 0::FLOAT AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time FROM ( SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, pid::TEXT, client_port, CASE state WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, CASE sync_state WHEN 'async' THEN 0 WHEN 'potential' THEN 1 WHEN 'sync' THEN 2 WHEN 'quorum' THEN 3 ELSE -1 END AS sync_state, sync_priority, backend_xmin::TEXT::BIGINT AS backend_xmin, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, (('x' || lpad(split_part(sent_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(sent_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS sent_lsn, (('x' || lpad(split_part(write_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(write_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS write_lsn, (('x' || lpad(split_part(flush_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(flush_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, (('x' || lpad(split_part(replay_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(replay_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS replay_lsn, backend_start FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90400 max_version: 100000 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback." } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (N/A on 9.x)" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it (N/A on 9.x)" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it (N/A on 9.x)" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } pg_repl_92: name: pg_repl desc: PostgreSQL replication stat metrics 9.2 - 9.3 query: |- SELECT appname, usename, address, pid, client_port, state, sync_state, sync_priority, backend_xmin, lsn, lsn - sent_lsn AS sent_diff, lsn - write_lsn AS write_diff, lsn - flush_lsn AS flush_diff, lsn - replay_lsn AS replay_diff, sent_lsn, write_lsn, flush_lsn, replay_lsn, 0::FLOAT AS write_lag, 0::FLOAT AS flush_lag, 0::FLOAT AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time FROM ( SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, pid::TEXT, client_port, CASE state WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, CASE sync_state WHEN 'async' THEN 0 WHEN 'potential' THEN 1 WHEN 'sync' THEN 2 WHEN 'quorum' THEN 3 ELSE -1 END AS sync_state, sync_priority, 0::BIGINT AS backend_xmin, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, (('x' || lpad(split_part(sent_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(sent_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS sent_lsn, (('x' || lpad(split_part(write_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(write_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS write_lsn, (('x' || lpad(split_part(flush_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(flush_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, (('x' || lpad(split_part(replay_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(replay_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS replay_lsn, backend_start FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90200 max_version: 90400 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback (N/A before 9.4)" } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (N/A on 9.x)" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it (N/A on 9.x)" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it (N/A on 9.x)" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } pg_repl_91: name: pg_repl desc: PostgreSQL replication stat metrics 9.1 (procpid, no state/sync columns) query: |- SELECT appname, usename, address, pid, client_port, state, sync_state, sync_priority, backend_xmin, lsn, lsn - sent_lsn AS sent_diff, lsn - write_lsn AS write_diff, lsn - flush_lsn AS flush_diff, lsn - replay_lsn AS replay_diff, sent_lsn, write_lsn, flush_lsn, replay_lsn, 0::FLOAT AS write_lag, 0::FLOAT AS flush_lag, 0::FLOAT AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time FROM ( SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, procpid::TEXT AS pid, client_port, 0::INT AS state, 0::INT AS sync_state, 0::INT AS sync_priority, 0::BIGINT AS backend_xmin, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, (('x' || lpad(split_part(sent_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(sent_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS sent_lsn, (('x' || lpad(split_part(write_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(write_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS write_lsn, (('x' || lpad(split_part(flush_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(flush_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, (('x' || lpad(split_part(replay_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(replay_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS replay_lsn, backend_start FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90100 max_version: 90200 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback (N/A before 9.4)" } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (N/A on 9.x)" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it (N/A on 9.x)" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it (N/A on 9.x)" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } pg_exporter-1.4.1/legacy/config/0220-pg_sync_standby.yml000066400000000000000000000013731523237665500231000ustar00rootroot00000000000000#==============================================================# # 0220 pg_sync_standby #==============================================================# pg_sync_standby: name: pg_sync_standby desc: PostgreSQL synchronous standby status and names query: |- SELECT CASE WHEN names <> '' THEN names ELSE '' END AS names, CASE WHEN names <> '' THEN 1 ELSE 0 END AS enabled FROM (SELECT current_setting('synchronous_standby_names') AS names) n; ttl: 10 min_version: 90100 tags: [ cluster ] metrics: - names: { usage: LABEL ,description: "List of standby servers that can support synchronous replication" } - enabled: { usage: GAUGE ,description: "Synchronous commit enabled, 1 if enabled, 0 if disabled" } pg_exporter-1.4.1/legacy/config/0230-pg_downstream.yml000066400000000000000000000017421523237665500225640ustar00rootroot00000000000000#==============================================================# # 0230 pg_downstream #==============================================================# pg_downstream: name: pg_downstream desc: PostgreSQL replication client count (no state column on 9.1) query: |- SELECT 'connected' AS state, count(*) AS count FROM pg_stat_replication; ttl: 10 min_version: 90100 max_version: 90200 tags: [ cluster ] metrics: - state: { usage: LABEL ,description: "Replication client state" } - count: { usage: GAUGE ,description: "Count of replication clients by state" } pg_downstream_92: name: pg_downstream desc: PostgreSQL replication client count (group by state) query: |- SELECT state, count(*) AS count FROM pg_stat_replication GROUP BY state; ttl: 10 min_version: 90200 tags: [ cluster ] metrics: - state: { usage: LABEL ,description: "Replication client state" } - count: { usage: GAUGE ,description: "Count of replication clients by state" } pg_exporter-1.4.1/legacy/config/0240-pg_slot.yml000066400000000000000000000217061523237665500213650ustar00rootroot00000000000000#==============================================================# # 0240 pg_slot #==============================================================# pg_slot_96: name: pg_slot desc: PostgreSQL replication slot metrics 9.6 (with active_pid, confirmed_flush_lsn) query: |- SELECT slot_name, slot_type, plugin, database AS datname, datoid, active_pid, active, FALSE AS temporary, xmin::TEXT::BIGINT AS xmin, catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn, confirm_lsn, current_lsn - restart_lsn AS retained_bytes FROM ( SELECT slot_name, slot_type, plugin, database, datoid, active_pid, active, xmin, catalog_xmin, (('x' || lpad(split_part(restart_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(restart_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS restart_lsn, (('x' || lpad(split_part(confirmed_flush_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(confirmed_flush_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS confirm_lsn, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS current_lsn FROM pg_replication_slots, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90600 max_version: 100000 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot (N/A on 9.x, always 0)" } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } pg_slot_95: name: pg_slot desc: PostgreSQL replication slot metrics 9.5 (no confirmed_flush_lsn) query: |- SELECT slot_name, slot_type, plugin, database AS datname, datoid, active_pid, active, FALSE AS temporary, xmin::TEXT::BIGINT AS xmin, catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn, confirm_lsn, current_lsn - restart_lsn AS retained_bytes FROM ( SELECT slot_name, slot_type, plugin, database, datoid, active_pid, active, xmin, catalog_xmin, (('x' || lpad(split_part(restart_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(restart_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS restart_lsn, 0::BIGINT AS confirm_lsn, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS current_lsn FROM pg_replication_slots, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90500 max_version: 90600 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot (N/A on 9.x, always 0)" } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "Confirmed flush lsn (N/A before 9.6, always 0)" } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } pg_slot_94: name: pg_slot desc: PostgreSQL replication slot metrics 9.4 (no active_pid, confirmed_flush_lsn) query: |- SELECT slot_name, slot_type, plugin, database AS datname, datoid, active_pid, active, FALSE AS temporary, xmin::TEXT::BIGINT AS xmin, catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn, confirm_lsn, current_lsn - restart_lsn AS retained_bytes FROM ( SELECT slot_name, slot_type, plugin, database, datoid, NULL::INT AS active_pid, active, xmin, catalog_xmin, (('x' || lpad(split_part(restart_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(restart_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS restart_lsn, 0::BIGINT AS confirm_lsn, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS current_lsn FROM pg_replication_slots, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90400 max_version: 90500 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "Process ID is not available before 9.5 (NULL)" } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot (N/A on 9.x, always 0)" } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "Confirmed flush lsn (N/A before 9.6, always 0)" } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } pg_exporter-1.4.1/legacy/config/0250-pg_recv.yml000066400000000000000000000064331523237665500213440ustar00rootroot00000000000000#==============================================================# # 0250 pg_recv #==============================================================# pg_recv_96: name: pg_recv desc: PostgreSQL walreceiver metrics (9.6 - 12) query: |- SELECT (regexp_match(conninfo, '.*host=(\S+).*'))[1] AS sender_host, (regexp_match(conninfo, '.*port=(\S+).*'))[1] AS sender_port, coalesce(slot_name, 'NULL') AS slot_name, pid, CASE status WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, (('x' || lpad(split_part(receive_start_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(receive_start_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS init_lsn, receive_start_tli AS init_tli, (('x' || lpad(split_part(received_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(received_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, received_tli AS flush_tli, (('x' || lpad(split_part(latest_end_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(latest_end_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS reported_lsn, last_msg_send_time AS msg_send_time, last_msg_receipt_time AS msg_recv_time, latest_end_time AS reported_time, now() AS time FROM pg_stat_wal_receiver; ttl: 10 min_version: 90600 max_version: 130000 tags: [ cluster, replica ] metrics: - sender_host: { usage: LABEL ,description: "Host of the PostgreSQL instance this WAL receiver is connected to" } - sender_port: { usage: LABEL ,description: "Port number of the PostgreSQL instance this WAL receiver is connected to." } - slot_name: { usage: LABEL ,description: "Replication slot name used by this WAL receiver" } - pid: { usage: GAUGE ,description: "Process ID of the WAL receiver process" } - state: { usage: GAUGE ,description: "Encoded activity status of the WAL receiver process 0-4 for streaming|startup|catchup|backup|stopping" } - init_lsn: { usage: COUNTER ,description: "First write-ahead log location used when WAL receiver is started" } - init_tli: { usage: COUNTER ,description: "First timeline number used when WAL receiver is started" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and flushed to disk" } - flush_tli: { usage: COUNTER ,description: "Timeline number of last write-ahead log location received and flushed to disk" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - time: { usage: GAUGE ,description: "Time of current snapshot" } pg_exporter-1.4.1/legacy/config/0270-pg_origin.yml000066400000000000000000000025121523237665500216700ustar00rootroot00000000000000#==============================================================# # 0270 pg_origin #==============================================================# # skip by default, require additional privilege setup # GRANT SELECT ON pg_replication_origin, pg_replication_origin_status TO pg_monitor; pg_origin: name: pg_origin desc: PostgreSQL replay state (approximate) for a certain origin query: |- SELECT roname, (('x' || lpad(split_part(remote_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(remote_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS remote_lsn, (('x' || lpad(split_part(local_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(local_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS local_lsn FROM pg_replication_origin o LEFT JOIN pg_replication_origin_status os ON o.roident = os.local_id; ttl: 10 min_version: 90500 skip: true tags: [ cluster ] metrics: - roname: { usage: LABEL ,description: "The external, user defined, name of a replication origin." } - remote_lsn: { usage: COUNTER ,description: "The origin node's LSN up to which data has been replicated." } - local_lsn: { usage: COUNTER ,description: "This node's LSN at which remote_lsn has been replicated." } pg_exporter-1.4.1/legacy/config/0310-pg_size.yml000066400000000000000000000007771523237665500213610ustar00rootroot00000000000000#==============================================================# # 0310 pg_size #==============================================================# pg_size: name: pg_size desc: PostgreSQL database size (legacy 9.1-9.6) query: |- SELECT datname, pg_database_size(oid) AS bytes FROM pg_database; ttl: 60 timeout: 1 min_version: 90100 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Database name" } - bytes: { usage: GAUGE ,description: "Database size in bytes" } pg_exporter-1.4.1/legacy/config/0320-pg_archiver.yml000066400000000000000000000021341523237665500222000ustar00rootroot00000000000000#==============================================================# # 0320 pg_archiver #==============================================================# pg_archiver: name: pg_archiver desc: PostgreSQL archiver process statistics query: |- SELECT archived_count AS finish_count,failed_count, extract(epoch FROM last_archived_time) AS finish_time, extract(epoch FROM last_failed_time) AS failed_time, extract(epoch FROM stats_reset) AS reset_time FROM pg_stat_archiver; ttl: 60 min_version: 90400 tags: [ cluster ] metrics: - finish_count: { usage: COUNTER ,description: "Number of WAL files that have been successfully archived" } - failed_count: { usage: COUNTER ,description: "Number of failed attempts for archiving WAL files" } - finish_time: { usage: GAUGE ,description: "Time of the last successful archive operation" } - failed_time: { usage: GAUGE ,description: "Time of the last failed archival operation" } - reset_time: { usage: GAUGE ,description: "Time at which archive statistics were last reset" } pg_exporter-1.4.1/legacy/config/0330-pg_bgwriter.yml000066400000000000000000000102421523237665500222220ustar00rootroot00000000000000#==============================================================# # 0330 pg_bgwriter #==============================================================# # https://pgpedia.info/p/pg_stat_bgwriter.html pg_bgwriter_94: name: pg_bgwriter desc: "PostgreSQL background writer metrics (PG 9.4-16)" query: SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint, buffers_clean, buffers_backend, maxwritten_clean, buffers_backend_fsync, buffers_alloc, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 90400 max_version: 170000 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - checkpoint_sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_checkpoint: { usage: COUNTER ,description: "Number of buffers written during checkpoints" } - buffers_clean: { usage: COUNTER ,description: "Number of buffers written by the background writer" } - buffers_backend: { usage: COUNTER ,description: "Number of buffers written directly by a backend" } - maxwritten_clean: { usage: COUNTER ,description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers" } - buffers_backend_fsync: { usage: COUNTER ,description: "Number of times a backend had to execute its own fsync call" } - buffers_alloc: { usage: COUNTER ,description: "Number of buffers allocated" } - reset_time: { usage: GAUGE ,description: "Time at which bgwriter statistics were last reset" } pg_bgwriter_91: name: pg_bgwriter desc: "PostgreSQL background writer metrics (PG 9.1-9.3)" query: SELECT checkpoints_timed, checkpoints_req, 0::BIGINT AS checkpoint_write_time, 0::BIGINT AS checkpoint_sync_time, buffers_checkpoint, buffers_clean, buffers_backend, maxwritten_clean, buffers_backend_fsync, buffers_alloc, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 90100 max_version: 90400 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time spent writing checkpoint files, in seconds (N/A on 9.1-9.3, always 0)" } - checkpoint_sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time spent syncing checkpoint files, in seconds (N/A on 9.1-9.3, always 0)" } - buffers_checkpoint: { usage: COUNTER ,description: "Number of buffers written during checkpoints" } - buffers_clean: { usage: COUNTER ,description: "Number of buffers written by the background writer" } - buffers_backend: { usage: COUNTER ,description: "Number of buffers written directly by a backend" } - maxwritten_clean: { usage: COUNTER ,description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers" } - buffers_backend_fsync: { usage: COUNTER ,description: "Number of times a backend had to execute its own fsync call" } - buffers_alloc: { usage: COUNTER ,description: "Number of buffers allocated" } - reset_time: { usage: GAUGE ,description: "Time at which bgwriter statistics were last reset" } pg_exporter-1.4.1/legacy/config/0331-pg_checkpointer.yml000066400000000000000000000056101523237665500230570ustar00rootroot00000000000000#==============================================================# # 0331 pg_checkpointer #==============================================================# pg_checkpointer_94: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 9.4-16" query: SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 90400 max_version: 170000 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,rename: write_time ,scale: 1e-3 ,description: "Total amount of time that has been spent writing checkpoint files, in seconds" } - checkpoint_sync_time: { usage: COUNTER ,rename: sync_time ,scale: 1e-3 ,description: "Total amount of time that has been spent synchronizing checkpoint files to disk, in seconds" } - buffers_checkpoint: { usage: COUNTER ,rename: buffers_written ,description: "Number of buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } pg_checkpointer_91: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 9.1-9.3" query: SELECT checkpoints_timed, checkpoints_req, 0::BIGINT AS checkpoint_write_time, 0::BIGINT AS checkpoint_sync_time, buffers_checkpoint, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 90100 max_version: 90400 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,rename: write_time ,scale: 1e-3 ,description: "Total amount of time that has been spent writing checkpoint files, in seconds (N/A on 9.1-9.3, always 0)" } - checkpoint_sync_time: { usage: COUNTER ,rename: sync_time ,scale: 1e-3 ,description: "Total amount of time that has been spent synchronizing checkpoint files to disk, in seconds (N/A on 9.1-9.3, always 0)" } - buffers_checkpoint: { usage: COUNTER ,rename: buffers_written ,description: "Number of buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } pg_exporter-1.4.1/legacy/config/0340-pg_ssl.yml000066400000000000000000000011501523237665500211750ustar00rootroot00000000000000#==============================================================# # 0340 pg_ssl #==============================================================# pg_ssl: name: pg_ssl desc: PostgreSQL SSL client connection count query: | SELECT count(*) FILTER (WHERE ssl) AS enabled, count(*) FILTER ( WHERE NOT ssl) AS disabled FROM pg_stat_ssl; ttl: 10 min_version: 90500 tags: [ cluster ] metrics: - enabled: { usage: GAUGE ,description: "Number of client connection that use ssl" } - disabled: { usage: GAUGE ,description: "Number of client connection that does not use ssl" } pg_exporter-1.4.1/legacy/config/0350-pg_checkpoint.yml000066400000000000000000000064221523237665500225330ustar00rootroot00000000000000#==============================================================# # 0350 pg_checkpoint #==============================================================# pg_checkpoint: name: pg_checkpoint desc: checkpoint information from pg_control_checkpoint (9.6) query: |- SELECT (('x' || lpad(split_part(checkpoint_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(checkpoint_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS checkpoint_lsn, (('x' || lpad(split_part(redo_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(redo_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS redo_lsn, timeline_id AS tli, prev_timeline_id AS prev_tli, full_page_writes, split_part(next_xid, ':', 1) AS next_xid_epoch, split_part(next_xid, ':', 2) AS next_xid, next_oid::BIGINT, next_multixact_id::text::BIGINT, next_multi_offset::text::BIGINT, oldest_xid::text::BIGINT, oldest_xid_dbid::text::BIGINT, oldest_active_xid::text::BIGINT, oldest_multi_xid::text::BIGINT, oldest_multi_dbid::BIGINT, oldest_commit_ts_xid::text::BIGINT, newest_commit_ts_xid::text::BIGINT, checkpoint_time AS time, extract(epoch from now() - checkpoint_time) AS elapse FROM pg_control_checkpoint(); ttl: 60 min_version: 90600 tags: [ cluster ] metrics: - checkpoint_lsn: { usage: COUNTER ,description: "Latest checkpoint location" } - redo_lsn: { usage: COUNTER ,description: "Latest checkpoint's REDO location" } - tli: { usage: COUNTER ,description: "Latest checkpoint's TimeLineID" } - prev_tli: { usage: COUNTER ,description: "Latest checkpoint's PrevTimeLineID" } - full_page_writes: { usage: GAUGE ,description: "Latest checkpoint's full_page_writes enabled" } - next_xid_epoch: { usage: COUNTER ,description: "Latest checkpoint's NextXID epoch" } - next_xid: { usage: COUNTER ,description: "Latest checkpoint's NextXID xid" } - next_oid: { usage: COUNTER ,description: "Latest checkpoint's NextOID" } - next_multixact_id: { usage: COUNTER ,description: "Latest checkpoint's NextMultiXactId" } - next_multi_offset: { usage: COUNTER ,description: "Latest checkpoint's NextMultiOffset" } - oldest_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestXID" } - oldest_xid_dbid: { usage: GAUGE ,description: "Latest checkpoint's oldestXID's DB OID" } - oldest_active_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestActiveXID" } - oldest_multi_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestMultiXid" } - oldest_multi_dbid: { usage: GAUGE ,description: "Latest checkpoint's oldestMulti's DB OID" } - oldest_commit_ts_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestCommitTsXid" } - newest_commit_ts_xid: { usage: COUNTER ,description: "Latest checkpoint's newestCommitTsXid" } - time: { usage: COUNTER ,description: "Time of latest checkpoint" } - elapse: { usage: GAUGE ,description: "Seconds elapsed since latest checkpoint in seconds" } pg_exporter-1.4.1/legacy/config/0355-pg_timeline.yml000066400000000000000000000010051523237665500222070ustar00rootroot00000000000000#==============================================================# # 0355 pg_timeline #==============================================================# pg_timeline: name: pg_timeline desc: Current timeline ID from primary or replica query: | SELECT COALESCE( (SELECT received_tli FROM pg_stat_wal_receiver), (SELECT timeline_id FROM pg_control_checkpoint()) ) AS id; ttl: 10 min_version: 90600 tags: [ cluster ] metrics: - id: { usage: GAUGE ,description: "Current timeline ID" } pg_exporter-1.4.1/legacy/config/0360-pg_recovery.yml000066400000000000000000000030161523237665500222370ustar00rootroot00000000000000#==============================================================# # 0360 pg_recovery #==============================================================# pg_recovery: name: pg_recovery desc: PostgreSQL control recovery metrics (9.6) query: | SELECT min_recovery_end_timeline AS min_timeline, (('x' || lpad(split_part(min_recovery_end_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(min_recovery_end_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS min_lsn, (('x' || lpad(split_part(backup_start_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(backup_start_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS backup_start_lsn, (('x' || lpad(split_part(backup_end_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(backup_end_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS backup_end_lsn, end_of_backup_record_required AS require_record FROM pg_control_recovery(); ttl: 10 min_version: 90600 tags: [ cluster, replica ] metrics: - min_timeline: { usage: COUNTER ,description: "Min recovery ending loc's timeline" } - min_lsn: { usage: COUNTER ,description: "Minimum recovery ending location" } - backup_start_lsn: { usage: COUNTER ,description: "Backup start location" } - backup_end_lsn: { usage: COUNTER ,description: "Backup end location" } - require_record: { usage: GAUGE ,description: "End-of-backup record required" } pg_exporter-1.4.1/legacy/config/0410-pg_activity.yml000066400000000000000000000057471523237665500222460ustar00rootroot00000000000000#==============================================================# # 0410 pg_activity #==============================================================# pg_activity_92: name: pg_activity desc: PostgreSQL backend activity group by database and state (9.2+) query: |- SELECT datname, state, coalesce(count, 0) AS count, coalesce(max_duration, 0) AS max_duration, coalesce(max_tx_duration, 0) AS max_tx_duration, coalesce(max_conn_duration, 0) AS max_conn_duration FROM (SELECT d.datname, a.state FROM pg_database d, unnest(ARRAY ['active','idle','idle in transaction','idle in transaction (aborted)','fastpath function call','disabled']) a(state) WHERE d.datallowconn AND NOT d.datistemplate) base LEFT JOIN (SELECT datname, state, count(*) AS count, max(extract(epoch from now() - state_change)) AS max_duration, max(extract(epoch from now() - xact_start)) AS max_tx_duration, max(extract(epoch from now() - backend_start)) AS max_conn_duration FROM pg_stat_activity WHERE pid <> pg_backend_pid() GROUP BY 1,2) data USING (datname,state); ttl: 10 min_version: 90200 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - state: { usage: LABEL ,description: "Current overall state of this backend." } - count: { usage: GAUGE ,description: "Count of connection among (datname,state)" } - max_duration: { usage: GAUGE ,description: "Max duration since last state change among (datname, state)" } - max_tx_duration: { usage: GAUGE ,description: "Max transaction duration since state change among (datname, state)" } - max_conn_duration: { usage: GAUGE ,description: "Max backend session duration since state change among (datname, state)" } pg_activity_91: name: pg_activity desc: PostgreSQL backend activity group by database (9.1) query: | SELECT datname, 'active' AS state, count(*) AS count, max(extract(epoch from now() - query_start)) AS max_duration, max(extract(epoch from now() - xact_start)) AS max_tx_duration, max(extract(epoch from now() - backend_start)) AS max_conn_duration FROM pg_stat_activity WHERE procpid <> pg_backend_pid() AND datname IS NOT NULL GROUP BY datname; ttl: 10 min_version: 90100 max_version: 90200 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - state: { usage: LABEL ,description: "Current overall state of this backend (always active on 9.1)" } - count: { usage: GAUGE ,description: "Count of connection among (datname,state)" } - max_duration: { usage: GAUGE ,description: "Max duration since query start among (datname)" } - max_tx_duration: { usage: GAUGE ,description: "Max transaction duration among (datname)" } - max_conn_duration: { usage: GAUGE ,description: "Max backend session duration among (datname)" } pg_exporter-1.4.1/legacy/config/0420-pg_wait.yml000066400000000000000000000025021523237665500213410ustar00rootroot00000000000000#==============================================================# # 0420 pg_wait #==============================================================# pg_wait_96: name: pg_wait desc: PostgreSQL backend client count group by wait event type (9.6) query: | SELECT coalesce(datname, '_system') AS datname, coalesce(wait_event_type, 'Running') AS event, count(*) AS count FROM pg_stat_activity GROUP BY 1, 2; ttl: 10 min_version: 90600 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database, _system for global process" } - event: { usage: LABEL ,description: "Wait event type" } - count: { usage: GAUGE ,description: "Count of WaitEvent on target database" } pg_wait_91: name: pg_wait desc: PostgreSQL backend client count group by waiting flag (9.1-9.5) query: | SELECT coalesce(datname, '_system') AS datname, CASE WHEN waiting THEN 'Waiting' ELSE 'Running' END AS event, count(*) AS count FROM pg_stat_activity GROUP BY 1, 2; ttl: 10 min_version: 90100 max_version: 90600 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database, _system for global process" } - event: { usage: LABEL ,description: "Waiting flag, Waiting or Running" } - count: { usage: GAUGE ,description: "Backend count group by waiting flag" } pg_exporter-1.4.1/legacy/config/0440-pg_xact.yml000066400000000000000000000016111523237665500213360ustar00rootroot00000000000000#==============================================================# # 0440 pg_xact #==============================================================# pg_xact: name: pg_xact desc: PostgreSQL transaction identifier metrics query: | WITH snap(v) AS (SELECT txid_current_snapshot()), xset(v) AS (SELECT txid_snapshot_xip(v) FROM snap), xnum(v) AS (SELECT count(*) FROM xset), xmin(v) AS (SELECT txid_snapshot_xmin(v) FROM snap), xmax(v) AS (SELECT txid_snapshot_xmax(v) FROM snap) SELECT xmin.v AS xmin, xmax.v AS xmax, xnum.v AS xnum FROM xmin, xmax, xnum; ttl: 10 min_version: 90100 tags: [ cluster ] metrics: - xmin: { usage: COUNTER ,description: "Earliest txid that is still active" } - xmax: { usage: COUNTER ,description: "First as-yet-unassigned txid" } - xnum: { usage: GAUGE ,description: "Current active transaction count" } pg_exporter-1.4.1/legacy/config/0450-pg_lock.yml000066400000000000000000000021171523237665500213320ustar00rootroot00000000000000#==============================================================# # 0450 pg_lock #==============================================================# pg_lock: name: pg_lock desc: PostgreSQL lock distribution by mode and database query: | SELECT datname, mode, coalesce(count, 0) AS count FROM (SELECT d.oid AS database, d.datname, l.mode FROM pg_database d, unnest(ARRAY ['AccessShareLock','RowShareLock','RowExclusiveLock','ShareUpdateExclusiveLock', 'ShareLock','ShareRowExclusiveLock','ExclusiveLock','AccessExclusiveLock']) l(mode) WHERE d.datallowconn AND NOT d.datistemplate) base LEFT JOIN (SELECT database, mode, count(*) AS count FROM pg_locks WHERE database IS NOT NULL GROUP BY 1, 2) cnt USING (database, mode); ttl: 10 min_version: 90100 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - mode: { usage: LABEL ,description: "Name of the lock mode held or desired by this process" } - count: { usage: GAUGE ,description: "Number of locks of corresponding mode and database" } pg_exporter-1.4.1/legacy/config/0460-pg_query.yml000066400000000000000000000073721523237665500215600ustar00rootroot00000000000000#==============================================================# # 0460 pg_query #==============================================================# pg_query_94: name: pg_query desc: PostgreSQL query statement metrics, require pg_stat_statements installed, 9.4 - 12 query: |- SELECT datname, queryid AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_time) AS exec_time, sum(blk_read_time) + sum(blk_write_time) AS io_time, sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, sum(shared_blks_dirtied) AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements(false) s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 90400 max_version: 130000 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "QueryID generated from internal hash code, computed from the statement's parse tree" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } pg_query_91: name: pg_query desc: PostgreSQL query statement metrics, require pg_stat_statements installed, 9.1 - 9.3 (no queryid) query: |- SELECT datname, md5(query) AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_time) AS exec_time, 0::FLOAT AS io_time, sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, 0::BIGINT AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 90100 max_version: 90400 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "MD5 hash of query text (no queryid before 9.4)" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds (N/A before 9.4, always 0)" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement (N/A before 9.4, always 0)" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } pg_exporter-1.4.1/legacy/config/0610-pg_db.yml000066400000000000000000000204001523237665500207600ustar00rootroot00000000000000#==============================================================# # 0610 pg_db #==============================================================# pg_db_92: name: pg_db desc: PostgreSQL database stats from pg_stat_database (9.2 - 9.6) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total, blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,blk_read_time,blk_write_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 90200 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_db_91: name: pg_db desc: PostgreSQL database stats from pg_stat_database (9.1, fewer columns) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total, blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts, 0::BIGINT AS temp_files, 0::BIGINT AS temp_bytes, 0::BIGINT AS deadlocks, 0::BIGINT AS blk_read_time, 0::BIGINT AS blk_write_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 90100 max_version: 90200 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database (N/A on 9.1, always 0)" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database (N/A on 9.1, always 0)" } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database (N/A on 9.1, always 0)" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds (N/A on 9.1, always 0)" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds (N/A on 9.1, always 0)" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_exporter-1.4.1/legacy/config/0620-pg_db_confl.yml000066400000000000000000000025221523237665500221470ustar00rootroot00000000000000#==============================================================# # 0620 pg_db_confl #==============================================================# # https://pgpedia.info/p/pg_stat_database_conflicts.html pg_db_confl: name: pg_db_confl desc: PostgreSQL database conflicts metrics for pg 9.1 - 9.6 query: SELECT datid,datname,confl_tablespace,confl_lock,confl_snapshot,confl_bufferpin,confl_deadlock FROM pg_stat_database_conflicts; ttl: 10 min_version: 90100 tags: [ cluster, replica ] metrics: - datid: { usage: DISCARD } - datname: { usage: LABEL ,description: "Name of this database" } - confl_tablespace: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to dropped tablespaces" } - confl_lock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to lock timeouts" } - confl_snapshot: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to old snapshots" } - confl_bufferpin: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to pinned buffers" } - confl_deadlock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to deadlocks" } pg_exporter-1.4.1/legacy/config/0700-pg_table.yml000066400000000000000000000317641523237665500215010ustar00rootroot00000000000000#==============================================================# # 0700 pg_table #==============================================================# pg_table_94: name: pg_table desc: PostgreSQL table metrics 9.4-9.6 query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 90400 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_table_91: name: pg_table desc: PostgreSQL table metrics 9.1-9.3 (no n_mod_since_analyze) query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_live_tup,psut.n_dead_tup, NULL::BIGINT AS n_mod_since_analyze, psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 90100 max_version: 90400 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,t/toast/116" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed (N/A on 9.1-9.3, NULL)" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_exporter-1.4.1/legacy/config/0710-pg_index.yml000066400000000000000000000043651523237665500215170ustar00rootroot00000000000000#==============================================================# # 0710 pg_index #==============================================================# pg_index: name: pg_index desc: PostgreSQL index metrics (legacy 9.1-9.6) query: |- SELECT CURRENT_CATALOG AS datname, psui.schemaname || '.' || psui.indexrelname AS idxname, psui.schemaname || '.' || psui.relname AS relname, psui.indexrelid AS relid, c.relpages, c.reltuples, psui.idx_scan, psui.idx_tup_read, psui.idx_tup_fetch, psio.idx_blks_read, psio.idx_blks_hit FROM pg_stat_user_indexes psui JOIN pg_statio_user_indexes psio ON psio.indexrelid = psui.indexrelid JOIN pg_class c ON c.oid = psui.indexrelid WHERE psui.schemaname !~ '^pg_' AND psui.schemaname !~ '^_' AND psui.schemaname !~ '^timescaledb' AND psui.schemaname !~ '^citus' AND psui.schemaname !~ '^columnar' AND psui.schemaname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') ORDER BY psui.idx_tup_read DESC LIMIT 512; ttl: 10 timeout: 1 min_version: 90100 metrics: - datname: { usage: LABEL ,description: "Database name of this index" } - idxname: { usage: LABEL ,description: "Name of this index (full-qualified schema name)" } - relname: { usage: LABEL ,description: "Name of the table for this index (full-qualified schema name)" } - relid: { usage: LABEL ,description: "Relation oid of this index" } - relpages: { usage: GAUGE ,description: "Size of the on-disk representation of this index in pages" } - reltuples: { usage: GAUGE ,description: "Estimate relation tuples" } - idx_scan: { usage: COUNTER ,description: "Number of index scans initiated on this index" } - idx_tup_read: { usage: COUNTER ,description: "Number of index entries returned by scans on this index" } - idx_tup_fetch: { usage: COUNTER ,description: "Number of live table rows fetched by simple index scans using this index" } - idx_blks_read: { usage: COUNTER ,description: "Number of disk blocks read from this index" } - idx_blks_hit: { usage: COUNTER ,description: "Number of buffer hits in this index" } pg_exporter-1.4.1/legacy/config/0720-pg_func.yml000066400000000000000000000021261523237665500213350ustar00rootroot00000000000000#==============================================================# # 0720 pg_func #==============================================================# pg_func: desc: PostgreSQL function metrics query: SELECT CURRENT_CATALOG AS datname, schemaname || '.' || funcname AS funcname, sum(calls) AS calls, sum(total_time) AS total_time, sum(self_time) AS self_time FROM pg_stat_user_functions GROUP BY 2 ORDER BY 4 DESC LIMIT 128; ttl: 10 min_version: 90100 metrics: - datname: { usage: LABEL ,description: "Name of belonged database" } - funcname: { usage: LABEL ,description: "Name of this function, may have multiple override" } - calls: { usage: COUNTER ,description: "Number of times this function has been called" } - total_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent in this function and all other functions called by it, in seconds" } - self_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent in this function itself, not including other functions called by it, in seconds" } pg_exporter-1.4.1/legacy/config/0740-pg_relkind.yml000066400000000000000000000011331523237665500220310ustar00rootroot00000000000000#==============================================================# # 0740 pg_relkind #==============================================================# pg_relkind: name: pg_relkind desc: Postgres relation count by kind query: | SELECT CURRENT_CATALOG AS datname, relkind, count(*) AS count FROM pg_class GROUP BY relkind; ttl: 60 timeout: 1 min_version: 90100 metrics: - datname: { usage: LABEL ,description: "Database name" } - relkind: { usage: LABEL ,description: "Relation kind (r,i,S,t,v,c,...)" } - count: { usage: GAUGE ,description: "Number of relations" } pg_exporter-1.4.1/legacy/config/0810-pg_table_size.yml000066400000000000000000000027071523237665500225300ustar00rootroot00000000000000#==============================================================# # 0810 pg_table_size #==============================================================# pg_table_size: desc: PostgreSQL table size metrics, quite slow query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || rel.relname AS relname, pg_total_relation_size(rel.oid) AS bytes, pg_relation_size(rel.oid) AS relsize, pg_indexes_size(rel.oid) AS indexsize, pg_total_relation_size(reltoastrelid) AS toastsize FROM pg_namespace nsp JOIN pg_class rel ON nsp.oid = rel.relnamespace WHERE nspname <> ALL(ARRAY['pg_catalog', 'information_schema']) AND rel.relkind = 'r' ORDER BY 3 DESC NULLS LAST LIMIT 256; ttl: 300 timeout: 2 min_version: 90100 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Schema qualified table name" } - bytes: { usage: GAUGE ,default: 0 ,description: "Total bytes of this table (including toast, index, toast index)" } - relsize: { usage: GAUGE ,default: 0 ,description: "Bytes of this table itself (main, vm, fsm)" } - indexsize: { usage: GAUGE ,default: 0 ,description: "Bytes of all related indexes of this table" } - toastsize: { usage: GAUGE ,default: 0 ,description: "Bytes of toast tables of this table" } pg_exporter-1.4.1/legacy/config/0820-pg_table_bloat.yml000066400000000000000000000016771523237665500226650ustar00rootroot00000000000000#==============================================================# # 0820 pg_table_bloat #==============================================================# # pg_table_bloat require auxiliary view to work. Disable it or create auxiliary view before use: pg_table_bloat: name: pg_table_bloat desc: PostgreSQL table bloat metrics, require auxiliary view pg_table_bloat to work query: SELECT datname, nspname || '.' || relname AS relname, size, ratio FROM pg_table_bloat ORDER BY size DESC LIMIT 64; ttl: 300 timeout: 2 min_version: 90400 skip: true metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Schema qualified name of this table" } - size: { usage: GAUGE ,description: "Total bytes of this table" } - ratio: { usage: GAUGE ,description: "Estimated bloat ratio of this table from 0 to 1" } pg_exporter-1.4.1/legacy/config/0830-pg_index_bloat.yml000066400000000000000000000016771523237665500227060ustar00rootroot00000000000000#==============================================================# # 0830 pg_index_bloat #==============================================================# # pg_index_bloat require auxiliary view to work. Disable it or create auxiliary view before use: pg_index_bloat: name: pg_index_bloat desc: PostgreSQL index bloat metrics, require auxiliary view pg_index_bloat to work query: SELECT datname, nspname || '.' || idxname AS idxname, size, ratio FROM pg_index_bloat ORDER BY size DESC LIMIT 64; ttl: 300 timeout: 2 min_version: 90400 skip: true metrics: - datname: { usage: LABEL ,description: "Database name of this index" } - idxname: { usage: LABEL ,description: "Schema qualified name of this index" } - size: { usage: GAUGE ,description: "Total bytes of this index" } - ratio: { usage: GAUGE ,description: "Estimated bloat ratio of this index from 0 to 1" } pg_exporter-1.4.1/legacy/config/0910-pgbouncer_list.yml000066400000000000000000000011201523237665500227250ustar00rootroot00000000000000#==============================================================# # 0910 pgbouncer_list #==============================================================# # http://www.pgbouncer.org/usage.html#show-lists pgbouncer_list: name: pgbouncer_list desc: Pgbouncer entry list query: SHOW LISTS; ttl: 10 min_version: 10800 fatal: true tags: [ pgbouncer ] metrics: - list: { usage: LABEL ,description: "Pgbouncer internal list name" } - items: { usage: GAUGE ,description: "Number of corresponding pgbouncer object" } pg_exporter-1.4.1/legacy/config/0920-pgbouncer_database.yml000066400000000000000000000167211523237665500235340ustar00rootroot00000000000000#==============================================================# # 0920 pgbouncer_database #==============================================================# # http://www.pgbouncer.org/usage.html#show-databases pgbouncer_database_124: name: pgbouncer_database desc: Pgbouncer database stats (since 1.24) query: SHOW DATABASES; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool_size: { usage: GAUGE ,rename: reserve_pool ,description: "Maximum number of additional connections for this database" } - server_lifetime: { usage: GAUGE ,description: "The maximum lifetime of a server connection for this database" } - pool_mode: { usage: DISCARD } - load_balance_hosts: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - max_client_connections: { usage: GAUGE ,description: "Maximum number of allowed client connections for this pgbouncer instance" } - current_client_connections: { usage: GAUGE ,description: "Current number of client connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_123: name: pgbouncer_database desc: Pgbouncer database stats 1.23 query: SHOW DATABASES; ttl: 10 min_version: 12300 max_version: 12400 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - server_lifetime: { usage: GAUGE ,description: "The maximum lifetime of a server connection for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_116: name: pgbouncer_database desc: Pgbouncer database stats (1.16-1.22) query: SHOW DATABASES; ttl: 10 min_version: 11600 max_version: 12300 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_108: name: pgbouncer_database desc: Pgbouncer database stats (1.08-1.15) query: SHOW DATABASES; ttl: 10 min_version: 10800 max_version: 11600 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pg_exporter-1.4.1/legacy/config/0930-pgbouncer_stat.yml000066400000000000000000000206311523237665500227370ustar00rootroot00000000000000#==============================================================# # 0930 pgbouncer_stat #==============================================================# # http://www.pgbouncer.org/usage.html#show-stats pgbouncer_stat_124: name: pgbouncer_stat desc: Pgbouncer stats per database (since 1.24) query: SHOW STATS; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_server_assignment_count: { usage: COUNTER ,description: "Total times a server was assigned to a client" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - total_client_parse_count: { usage: COUNTER ,description: "Total number of prepared statements created by clients" } - total_server_parse_count: { usage: COUNTER ,description: "Total number of prepared statements created on a server." } - total_bind_count: { usage: COUNTER ,description: "Total number of prepared statements readied for execution by clients and forwarded to postgres" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_server_assignment_count: { usage: GAUGE ,description: "Average number of times a server as assigned to a client per second in the last stat period." } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } - avg_client_parse_count: { usage: GAUGE ,description: "Average number of prepared statements created by clients" } - avg_server_parse_count: { usage: GAUGE ,description: "Average number of prepared statements created on a server." } - avg_bind_count: { usage: GAUGE ,description: "Average number of prepared statements readied for execution by clients and forwarded to postgres" } pgbouncer_stat_123: name: pgbouncer_stat desc: Pgbouncer stats per database (1.23) query: SHOW STATS; ttl: 10 min_version: 12300 max_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_server_assignment_count: { usage: COUNTER ,description: "Total times a server was assigned to a client" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_server_assignment_count: { usage: GAUGE ,description: "Average number of times a server as assigned to a client per second in the last stat period." } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } pgbouncer_stat_108: name: pgbouncer_stat desc: Pgbouncer stats per database (1.08 - 1.22) query: SHOW STATS; ttl: 10 min_version: 10800 max_version: 12300 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } pg_exporter-1.4.1/legacy/config/0940-pgbouncer_pool.yml000066400000000000000000000232451523237665500227420ustar00rootroot00000000000000#==============================================================# # 0940 pgbouncer_pool #==============================================================# # http://www.pgbouncer.org/usage.html#show-pools pgbouncer_pool_124: name: pgbouncer_pool desc: Pgbouncer pool stats (1.24+) query: SHOW POOLS; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_active_cancel_req: { usage: GAUGE, rename: active_cancel_clients, description: "Client connections that have forwarded query cancellations to the server and are waiting for the server response." } - cl_waiting_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_active_cancel: { usage: GAUGE, rename: active_cancel_servers, description: "Server connections that are currently forwarding a cancel request" } - sv_being_canceled: { usage: GAUGE, rename: cancel_servers, description: "cancel requests have completed that were sent to cancel a query on this server" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } - load_balance_hosts: { usage: LABEL, description: "The load_balance_hosts in use" } pgbouncer_pool_118: name: pgbouncer_pool desc: Pgbouncer pool stats (1.18-1.23) query: SHOW POOLS; ttl: 10 min_version: 11800 max_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_active_cancel_req: { usage: GAUGE, rename: active_cancel_clients, description: "Client connections that have forwarded query cancellations to the server and are waiting for the server response." } - cl_waiting_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_active_cancel: { usage: GAUGE, rename: active_cancel_servers, description: "Server connections that are currently forwarding a cancel request" } - sv_being_canceled: { usage: GAUGE, rename: cancel_servers, description: "cancel requests have completed that were sent to cancel a query on this server" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pgbouncer_pool_116: name: pgbouncer_pool desc: Pgbouncer pool stats (1.16-1.17) query: SHOW POOLS; ttl: 10 min_version: 11600 max_version: 11800 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pgbouncer_pool_108: name: pgbouncer_pool desc: Pgbouncer pool stats (1.08-1.15) query: SHOW POOLS; ttl: 10 min_version: 10800 max_version: 11600 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pg_exporter-1.4.1/legacy/config/1800-pg_tsdb_hypertable.yml000066400000000000000000000024671523237665500235650ustar00rootroot00000000000000#==============================================================# # 1800 pg_tsdb_hypertable #==============================================================# # this collector reqires timescaledb extension to be installed pg_tsdb_hypertable: name: pg_tsdb_hypertable desc: TimescaleDB hypertable overview query: |- SELECT current_database() AS datname, format('%I.%I', hypertable_schema, hypertable_name) AS relname, num_dimensions AS dimensions, num_chunks AS chunks, compression_enabled::BOOLEAN::int AS compressed, hypertable_size(format('"%I"."%I"', hypertable_schema, hypertable_name)::RegClass) AS bytes FROM timescaledb_information.hypertables; ttl: 60 timeout: 2 min_version: 90600 skip: true tags: [ "extension:timescaledb", "schema:timescaledb_information" ] metrics: - datname: { usage: LABEL ,description: "database name" } - relname: { usage: LABEL ,description: "Hypertable relation name" } - dimensions: { usage: GAUGE ,description: "Number of partitioning dimensions" } - chunks: { usage: GAUGE ,description: "Total chunks of this hypertable" } - compressed: { usage: GAUGE ,description: "1 if compression enabled" } - bytes: { usage: GAUGE ,description: "Total size of hypertable in bytes" } pg_exporter-1.4.1/legacy/config/1900-pg_citus.yml000066400000000000000000000027061523237665500215360ustar00rootroot00000000000000#==============================================================# # 1900 pg_citus_node #==============================================================# # https://docs.citusdata.com/en/latest/develop/api_metadata.html#worker-node-table pg_citus_node: name: pg_citus_node desc: Citus worker coordinator node inventory query: |- SELECT CONCAT(nodename, ':', nodeport) AS node, current_database() AS datname, nodeid AS id, groupid AS group, hasmetadata::BOOLEAN::INT AS has_meta, isactive::BOOLEAN::INT AS is_active, metadatasynced::BOOLEAN::INT AS meta_synced, shouldhaveshards::BOOLEAN::INT AS have_shards FROM pg_dist_node; ttl: 60 min_version: 90600 tags: [ "extension:citus" ] metrics: - node: { usage: LABEL ,description: "nodename:port of the PostgreSQL instance" } - datname: { usage: LABEL ,description: "database name" } - id: { usage: GAUGE ,description: "auto‑generated node identifier" } - group: { usage: GAUGE ,description: "replication group id (primary + secondaries)" } - has_meta: { usage: GAUGE ,description: "1 = internal use flag set" } - is_active: { usage: GAUGE ,description: "1 = node currently accepts shards" } - meta_synced: { usage: GAUGE ,description: "1 = metadata fully synced to node" } - have_shards: { usage: GAUGE ,description: "1 = rebalancer may place shards here" } pg_exporter-1.4.1/legacy/config/2000-pg_heartbeat.yml000066400000000000000000000020731523237665500223330ustar00rootroot00000000000000#==============================================================# # 2000 heartbeat #==============================================================# # this is a example of application monitoring and predicate queries pg_heartbeat: name: pg_heartbeat desc: monitoring heartbeat in monitor.heartbeat table predicate_queries: - name: if heartbeat table exists predicate_query: | SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'monitor' AND table_name = 'heartbeat'); query: |- SELECT id AS cluster_name, extract(EPOCH FROM ts) AS ts, lsn, txid FROM monitor.heartbeat; ttl: 10 min_version: 90100 tags: [ "dbname:postgres", "schema:monitor" ] skip: true metrics: - cluster_name: { usage: LABEL ,description: "cluster_name param of this database cluster" } - ts: { usage: GAUGE ,description: "unix timestamp of the heartbeat" } - lsn: { usage: COUNTER ,description: "lsn of the heartbeat" } - txid: { usage: GAUGE ,description: "txid of the heartbeat" } pg_exporter-1.4.1/legacy/pg_exporter.yml000066400000000000000000004274741523237665500204400ustar00rootroot00000000000000#==============================================================# # Desc : pg_exporter metrics collector definition (Legacy) # Ver : PostgreSQL 9.1 ~ 9.6 and pgbouncer 1.9~1.25+ # Ctime : 2019-12-09 # Mtime : 2026-02-07 # Homepage : https://pigsty.io # Author : Ruohang Feng (rh@vonng.com) # License : Apache-2.0 @ https://github.com/pgsty/pg_exporter # Copyright : 2018-2026 Ruohang Feng / Vonng (rh@vonng.com) #==============================================================# #==============================================================# # 1. Config File #==============================================================# # The configuration file for pg_exporter is a YAML file. # Default configurations are retrieved via following precedence: # 1. command line args: --config= # 2. environment variables: PG_EXPORTER_CONFIG= # 3. pg_exporter.yml (Current directory) # 4. /etc/pg_exporter.yml (config file) # 5. /etc/pg_exporter (config dir) #==============================================================# # 2. Config Format #==============================================================# # pg_exporter config could be a single YAML file, or a directory containing a series of separated YAML files. # Each YAML config file consists of one or more metrics Collector definition, which are top-level objects. # If a directory is provided, all YAML in that directory will be merged in alphabetic order. #==============================================================# # 3. Version Compatibility #==============================================================# # Each collector has two optional version compatibility parameters: `min_version` and `max_version`. # These two parameters specify the version compatibility of the collector. If target postgres/pgbouncer's # version is less than `min_version`, or higher than `max_version`, the collector will not be installed. # # These two parameters are using PostgreSQL server version number format, which is a 6-digit integer # format as :. # # For example: # - 90100 stands for 9.1 # - 90600 stands for 9.6 # - 100000 stands for 10.0 # # Version compatibility range is left-inclusive right-exclusive: [min, max) #==============================================================# # 0110 pg #==============================================================# pg_primary_only: name: pg desc: PostgreSQL basic information (on primary) query: |- SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, extract(EPOCH FROM pg_postmaster_start_time()) AS boot_time, (('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, (('x' || lpad(split_part(pg_current_xlog_insert_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_current_xlog_insert_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS insert_lsn, (('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS write_lsn, (('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_current_xlog_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, NULL::BIGINT AS receive_lsn, NULL::BIGINT AS replay_lsn, extract(EPOCH FROM pg_conf_load_time()) AS reload_time, extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, NULL::FLOAT AS last_replay_time, 0::FLOAT AS lag, pg_is_in_recovery() AS is_in_recovery, FALSE AS is_wal_replay_paused; tags: [ cluster, primary ] ttl: 1 min_version: 90100 max_version: 100000 fatal: true skip: false metrics: - timestamp: { usage: GAUGE ,description: "current database timestamp in unix epoch" } - uptime: { usage: GAUGE ,description: "seconds since postmaster start" } - boot_time: { usage: GAUGE ,description: "postmaster boot timestamp in unix epoch" } - lsn: { usage: COUNTER ,description: "log sequence number, current write location" } - insert_lsn: { usage: COUNTER ,description: "primary only, location of current wal inserting" } - write_lsn: { usage: COUNTER ,description: "primary only, location of current wal writing" } - flush_lsn: { usage: COUNTER ,description: "primary only, location of current wal syncing" } - receive_lsn: { usage: COUNTER ,description: "replica only, location of wal synced to disk" } - replay_lsn: { usage: COUNTER ,description: "replica only, location of wal applied" } - reload_time: { usage: GAUGE ,description: "time when configuration was last reloaded" } - conf_reload_time: { usage: GAUGE ,description: "seconds since last configuration reload" } - last_replay_time: { usage: GAUGE ,description: "time when last transaction been replayed" } - lag: { usage: GAUGE ,description: "replica only, replication lag in seconds" } - is_in_recovery: { usage: GAUGE ,description: "1 if in recovery mode" } - is_wal_replay_paused: { usage: GAUGE ,description: "1 if wal play is paused" } pg_replica_only: name: pg desc: PostgreSQL basic information (on replica, 9.1+) query: |- SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, extract(EPOCH FROM pg_postmaster_start_time()) AS boot_time, (('x' || lpad(split_part(pg_last_xlog_replay_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_last_xlog_replay_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, NULL::BIGINT AS insert_lsn, NULL::BIGINT AS write_lsn, NULL::BIGINT AS flush_lsn, (('x' || lpad(split_part(pg_last_xlog_receive_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_last_xlog_receive_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS receive_lsn, (('x' || lpad(split_part(pg_last_xlog_replay_location()::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(pg_last_xlog_replay_location()::text, '/', 2), 8, '0'))::bit(32)::bigint) AS replay_lsn, extract(EPOCH FROM pg_conf_load_time()) AS reload_time, extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, extract(EPOCH FROM pg_last_xact_replay_timestamp()) AS last_replay_time, CASE WHEN pg_last_xlog_receive_location() = pg_last_xlog_replay_location() THEN 0 ELSE EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()) END AS lag, pg_is_in_recovery() AS is_in_recovery, pg_is_xlog_replay_paused() AS is_wal_replay_paused; tags: [ cluster, replica ] ttl: 1 min_version: 90100 max_version: 100000 fatal: true skip: false metrics: - timestamp: { usage: GAUGE ,description: "current database timestamp in unix epoch" } - uptime: { usage: GAUGE ,description: "seconds since postmaster start" } - boot_time: { usage: GAUGE ,description: "postmaster boot timestamp in unix epoch" } - lsn: { usage: COUNTER ,description: "log sequence number, current write location" } - insert_lsn: { usage: COUNTER ,description: "primary only, location of current wal inserting" } - write_lsn: { usage: COUNTER ,description: "primary only, location of current wal writing" } - flush_lsn: { usage: COUNTER ,description: "primary only, location of current wal syncing" } - receive_lsn: { usage: COUNTER ,description: "replica only, location of wal synced to disk" } - replay_lsn: { usage: COUNTER ,description: "replica only, location of wal applied" } - reload_time: { usage: GAUGE ,description: "time when configuration was last reloaded" } - conf_reload_time: { usage: GAUGE ,description: "seconds since last configuration reload" } - last_replay_time: { usage: GAUGE ,description: "time when last transaction been replayed" } - lag: { usage: GAUGE ,description: "replica only, replication lag in seconds" } - is_in_recovery: { usage: GAUGE ,description: "1 if in recovery mode" } - is_wal_replay_paused: { usage: GAUGE ,description: "1 if wal play is paused" } #==============================================================# # 0120 pg_meta #==============================================================# pg_meta_96: name: pg_meta desc: PostgreSQL meta info for pg 9.6 (with pg_control_system) query: | SELECT (SELECT system_identifier FROM pg_control_system()) AS cluster_id, coalesce((SELECT setting FROM pg_settings WHERE name = 'cluster_name'), 'N/A') AS cluster_name, (SELECT setting FROM pg_settings WHERE name = 'port') AS listen_port, (SELECT setting FROM pg_settings WHERE name = 'data_directory') AS data_dir, (SELECT setting FROM pg_settings WHERE name = 'config_file') AS conf_path, (SELECT setting FROM pg_settings WHERE name = 'hba_file') AS hba_path, (SELECT setting FROM pg_settings WHERE name = 'wal_level') AS wal_level, (SELECT setting FROM pg_settings WHERE name = 'server_encoding') AS encoding, (SELECT setting FROM pg_settings WHERE name = 'server_version') AS version, (SELECT setting FROM pg_settings WHERE name = 'server_version_num') AS ver_num, version() AS ver_str, (SELECT setting FROM pg_settings WHERE name = 'shared_preload_libraries') AS extensions, coalesce((SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'), 'N/A') AS primary_conninfo, 1 AS info; ttl: 10 min_version: 90600 tags: [ cluster ] metrics: - cluster_id: { usage: LABEL ,description: "cluster system identifier" } - cluster_name: { usage: LABEL ,description: "cluster name" } - listen_port: { usage: LABEL ,description: "listen port" } - data_dir: { usage: LABEL ,description: "path to data directory" } - conf_path: { usage: LABEL ,description: "path to postgresql.conf" } - hba_path: { usage: LABEL ,description: "path to pg_hba.conf" } - wal_level: { usage: LABEL ,description: "wal level" } - encoding: { usage: LABEL ,description: "server encoding" } - version: { usage: LABEL ,description: "server version in human-readable format" } - ver_num: { usage: LABEL ,description: "server version number in machine-readable format" } - ver_str: { usage: LABEL ,description: "complete version string" } - extensions: { usage: LABEL ,description: "server installed preload libraries" } - primary_conninfo: { usage: LABEL ,description: "connection string to upstream (do not set password here)" } - info: { usage: GAUGE ,description: "constant 1" } pg_meta_91: name: pg_meta desc: PostgreSQL meta info for pg 9.1 - 9.5 query: | SELECT 'N/A' AS cluster_id, coalesce((SELECT setting FROM pg_settings WHERE name = 'cluster_name'), 'N/A') AS cluster_name, (SELECT setting FROM pg_settings WHERE name = 'port') AS listen_port, (SELECT setting FROM pg_settings WHERE name = 'data_directory') AS data_dir, (SELECT setting FROM pg_settings WHERE name = 'config_file') AS conf_path, (SELECT setting FROM pg_settings WHERE name = 'hba_file') AS hba_path, (SELECT setting FROM pg_settings WHERE name = 'wal_level') AS wal_level, (SELECT setting FROM pg_settings WHERE name = 'server_encoding') AS encoding, (SELECT setting FROM pg_settings WHERE name = 'server_version') AS version, (SELECT setting FROM pg_settings WHERE name = 'server_version_num') AS ver_num, version() AS ver_str, (SELECT setting FROM pg_settings WHERE name = 'shared_preload_libraries') AS extensions, coalesce((SELECT setting FROM pg_settings WHERE name = 'primary_conninfo'), 'N/A') AS primary_conninfo, 1 AS info; ttl: 10 min_version: 90100 max_version: 90600 tags: [ cluster ] metrics: - cluster_id: { usage: LABEL ,description: "cluster system identifier" } - cluster_name: { usage: LABEL ,description: "cluster name" } - listen_port: { usage: LABEL ,description: "listen port" } - data_dir: { usage: LABEL ,description: "path to data directory" } - conf_path: { usage: LABEL ,description: "path to postgresql.conf" } - hba_path: { usage: LABEL ,description: "path to pg_hba.conf" } - wal_level: { usage: LABEL ,description: "wal level" } - encoding: { usage: LABEL ,description: "server encoding" } - version: { usage: LABEL ,description: "server version in human-readable format" } - ver_num: { usage: LABEL ,description: "server version number in machine-readable format" } - ver_str: { usage: LABEL ,description: "complete version string" } - extensions: { usage: LABEL ,description: "server installed preload libraries" } - primary_conninfo: { usage: LABEL ,description: "connection string to upstream (do not set password here)" } - info: { usage: GAUGE ,description: "constant 1" } #==============================================================# # 0130 pg_setting #==============================================================# # Key PostgreSQL configuration parameters for PostgreSQL 9.1 - 9.6 # Use scalar subquery on pg_settings for "missing_ok" semantics (return NULL if not exist) pg_setting: name: pg_setting desc: PostgreSQL shared configuration parameters (legacy 9.1-9.6) query: | SELECT (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_connections, (SELECT setting::int FROM pg_settings WHERE name = 'max_prepared_transactions') AS max_prepared_transactions, (SELECT setting::int FROM pg_settings WHERE name = 'max_locks_per_transaction') AS max_locks_per_transaction, (SELECT setting::int FROM pg_settings WHERE name = 'max_worker_processes') AS max_worker_processes, (SELECT setting::int FROM pg_settings WHERE name = 'max_parallel_workers') AS max_parallel_workers, (SELECT setting::int FROM pg_settings WHERE name = 'max_parallel_workers_per_gather') AS max_parallel_workers_per_gather, (SELECT setting::int FROM pg_settings WHERE name = 'max_parallel_maintenance_workers') AS max_parallel_maintenance_workers, (SELECT setting::int FROM pg_settings WHERE name = 'max_replication_slots') AS max_replication_slots, (SELECT setting::int FROM pg_settings WHERE name = 'max_wal_senders') AS max_wal_senders, (SELECT setting::int FROM pg_settings WHERE name = 'block_size') AS block_size, (SELECT setting::int FROM pg_settings WHERE name = 'wal_block_size') AS wal_block_size, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'segment_size') AS segment_size, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'wal_segment_size') AS wal_segment_size, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'data_checksums') AS data_checksums, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'wal_log_hints') AS wal_log_hints, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'fsync') AS fsync, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'full_page_writes') AS full_page_writes, (SELECT CASE setting WHEN 'minimal' THEN 1 WHEN 'archive' THEN 2 WHEN 'hot_standby' THEN 3 ELSE 0 END FROM pg_settings WHERE name = 'wal_level') AS wal_level, (SELECT setting::int FROM pg_settings WHERE name = 'checkpoint_segments') AS checkpoint_segments, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'min_wal_size') AS min_wal_size, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'max_wal_size') AS max_wal_size, (SELECT setting::int FROM pg_settings WHERE name = 'wal_keep_segments') AS wal_keep_segments, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'shared_buffers') AS shared_buffers, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'work_mem') AS work_mem, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'maintenance_work_mem') AS maintenance_work_mem, (SELECT setting::bigint * CASE unit WHEN '8kB' THEN 8192 WHEN 'kB' THEN 1024 WHEN 'MB' THEN 1048576 WHEN 'GB' THEN 1073741824 ELSE 1 END FROM pg_settings WHERE name = 'effective_cache_size') AS effective_cache_size, (SELECT CASE setting WHEN 'off' THEN 0 WHEN 'on' THEN 1 WHEN 'always' THEN 2 ELSE -1 END FROM pg_settings WHERE name = 'archive_mode') AS archive_mode, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'autovacuum') AS autovacuum, (SELECT setting::int FROM pg_settings WHERE name = 'autovacuum_max_workers') AS autovacuum_max_workers, (SELECT setting::int FROM pg_settings WHERE name = 'checkpoint_timeout') AS checkpoint_timeout, (SELECT setting::float FROM pg_settings WHERE name = 'checkpoint_completion_target') AS checkpoint_completion_target, (SELECT CASE setting WHEN 'on' THEN 1 ELSE 0 END FROM pg_settings WHERE name = 'hot_standby') AS hot_standby, (SELECT CASE setting WHEN 'off' THEN 0 WHEN 'local' THEN 1 WHEN 'remote_write' THEN 2 WHEN 'on' THEN 3 WHEN 'remote_apply' THEN 4 ELSE -1 END FROM pg_settings WHERE name = 'synchronous_commit') AS synchronous_commit; ttl: 10 min_version: 90100 tags: [ cluster ] metrics: - max_connections: { usage: GAUGE ,description: "maximum number of concurrent connections to the database server" } - max_prepared_transactions: { usage: GAUGE ,description: "maximum number of transactions that can be in the prepared state simultaneously" } - max_locks_per_transaction: { usage: GAUGE ,description: "maximum number of locks per transaction" } - max_worker_processes: { usage: GAUGE ,description: "maximum number of background processes (9.4+)" } - max_parallel_workers: { usage: GAUGE ,description: "maximum number of parallel workers that can be active at one time (9.6+)" } - max_parallel_workers_per_gather: { usage: GAUGE ,description: "maximum number of parallel workers per Gather node (9.6+)" } - max_parallel_maintenance_workers: { usage: GAUGE ,description: "maximum number of parallel maintenance workers (NULL on 9.x)" } - max_replication_slots: { usage: GAUGE ,description: "maximum number of replication slots (9.4+)" } - max_wal_senders: { usage: GAUGE ,description: "maximum number of concurrent WAL sender connections" } - block_size: { usage: GAUGE ,description: "database block size in bytes (default 8192)" } - wal_block_size: { usage: GAUGE ,description: "WAL block size in bytes" } - segment_size: { usage: GAUGE ,description: "database file segment size in bytes" } - wal_segment_size: { usage: GAUGE ,description: "WAL segment size in bytes" } - data_checksums: { usage: GAUGE ,description: "data checksums enabled, 1=on 0=off (9.3+)" } - wal_log_hints: { usage: GAUGE ,description: "WAL log hints enabled, 1=on 0=off (9.4+)" } - fsync: { usage: GAUGE ,description: "fsync enabled (CRITICAL for data safety), 1=on 0=off" } - full_page_writes: { usage: GAUGE ,description: "full page writes enabled, 1=on 0=off" } - wal_level: { usage: GAUGE ,description: "WAL level, 1=minimal 2=archive 3=hot_standby" } - checkpoint_segments: { usage: GAUGE ,description: "number of checkpoint segments (pre-9.5)" } - min_wal_size: { usage: GAUGE ,description: "minimum WAL size in bytes (9.5+)" } - max_wal_size: { usage: GAUGE ,description: "maximum WAL size in bytes (9.5+)" } - wal_keep_segments: { usage: GAUGE ,description: "WAL segments kept for standby replication (pg_basebackup/streaming)" } - shared_buffers: { usage: GAUGE ,description: "shared buffer size in bytes" } - work_mem: { usage: GAUGE ,description: "work memory size in bytes" } - maintenance_work_mem: { usage: GAUGE ,description: "maintenance work memory size in bytes" } - effective_cache_size: { usage: GAUGE ,description: "planner's assumption about effective OS cache size in bytes" } - archive_mode: { usage: GAUGE ,description: "archive mode, 0=off 1=on 2=always" } - autovacuum: { usage: GAUGE ,description: "autovacuum enabled, 1=on 0=off" } - autovacuum_max_workers: { usage: GAUGE ,description: "maximum number of autovacuum worker processes" } - checkpoint_timeout: { usage: GAUGE ,description: "checkpoint timeout in seconds" } - checkpoint_completion_target: { usage: GAUGE ,description: "checkpoint completion target (0.0-1.0)" } - hot_standby: { usage: GAUGE ,description: "hot standby mode enabled, 1=on 0=off" } - synchronous_commit: { usage: GAUGE ,description: "synchronous commit level, 0=off 1=local 2=remote_write 3=on 4=remote_apply" } #==============================================================# # 0210 pg_repl #==============================================================# pg_repl_94: name: pg_repl desc: PostgreSQL replication stat metrics 9.4 - 9.6 (with backend_xmin) query: |- SELECT appname, usename, address, pid, client_port, state, sync_state, sync_priority, backend_xmin, lsn, lsn - sent_lsn AS sent_diff, lsn - write_lsn AS write_diff, lsn - flush_lsn AS flush_diff, lsn - replay_lsn AS replay_diff, sent_lsn, write_lsn, flush_lsn, replay_lsn, 0::FLOAT AS write_lag, 0::FLOAT AS flush_lag, 0::FLOAT AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time FROM ( SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, pid::TEXT, client_port, CASE state WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, CASE sync_state WHEN 'async' THEN 0 WHEN 'potential' THEN 1 WHEN 'sync' THEN 2 WHEN 'quorum' THEN 3 ELSE -1 END AS sync_state, sync_priority, backend_xmin::TEXT::BIGINT AS backend_xmin, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, (('x' || lpad(split_part(sent_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(sent_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS sent_lsn, (('x' || lpad(split_part(write_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(write_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS write_lsn, (('x' || lpad(split_part(flush_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(flush_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, (('x' || lpad(split_part(replay_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(replay_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS replay_lsn, backend_start FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90400 max_version: 100000 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback." } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (N/A on 9.x)" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it (N/A on 9.x)" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it (N/A on 9.x)" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } pg_repl_92: name: pg_repl desc: PostgreSQL replication stat metrics 9.2 - 9.3 query: |- SELECT appname, usename, address, pid, client_port, state, sync_state, sync_priority, backend_xmin, lsn, lsn - sent_lsn AS sent_diff, lsn - write_lsn AS write_diff, lsn - flush_lsn AS flush_diff, lsn - replay_lsn AS replay_diff, sent_lsn, write_lsn, flush_lsn, replay_lsn, 0::FLOAT AS write_lag, 0::FLOAT AS flush_lag, 0::FLOAT AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time FROM ( SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, pid::TEXT, client_port, CASE state WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, CASE sync_state WHEN 'async' THEN 0 WHEN 'potential' THEN 1 WHEN 'sync' THEN 2 WHEN 'quorum' THEN 3 ELSE -1 END AS sync_state, sync_priority, 0::BIGINT AS backend_xmin, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, (('x' || lpad(split_part(sent_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(sent_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS sent_lsn, (('x' || lpad(split_part(write_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(write_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS write_lsn, (('x' || lpad(split_part(flush_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(flush_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, (('x' || lpad(split_part(replay_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(replay_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS replay_lsn, backend_start FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90200 max_version: 90400 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback (N/A before 9.4)" } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (N/A on 9.x)" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it (N/A on 9.x)" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it (N/A on 9.x)" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } pg_repl_91: name: pg_repl desc: PostgreSQL replication stat metrics 9.1 (procpid, no state/sync columns) query: |- SELECT appname, usename, address, pid, client_port, state, sync_state, sync_priority, backend_xmin, lsn, lsn - sent_lsn AS sent_diff, lsn - write_lsn AS write_diff, lsn - flush_lsn AS flush_diff, lsn - replay_lsn AS replay_diff, sent_lsn, write_lsn, flush_lsn, replay_lsn, 0::FLOAT AS write_lag, 0::FLOAT AS flush_lag, 0::FLOAT AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time FROM ( SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, procpid::TEXT AS pid, client_port, 0::INT AS state, 0::INT AS sync_state, 0::INT AS sync_priority, 0::BIGINT AS backend_xmin, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS lsn, (('x' || lpad(split_part(sent_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(sent_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS sent_lsn, (('x' || lpad(split_part(write_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(write_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS write_lsn, (('x' || lpad(split_part(flush_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(flush_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, (('x' || lpad(split_part(replay_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(replay_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS replay_lsn, backend_start FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90100 max_version: 90200 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback (N/A before 9.4)" } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (N/A on 9.x)" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it (N/A on 9.x)" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it (N/A on 9.x)" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } #==============================================================# # 0220 pg_sync_standby #==============================================================# pg_sync_standby: name: pg_sync_standby desc: PostgreSQL synchronous standby status and names query: |- SELECT CASE WHEN names <> '' THEN names ELSE '' END AS names, CASE WHEN names <> '' THEN 1 ELSE 0 END AS enabled FROM (SELECT current_setting('synchronous_standby_names') AS names) n; ttl: 10 min_version: 90100 tags: [ cluster ] metrics: - names: { usage: LABEL ,description: "List of standby servers that can support synchronous replication" } - enabled: { usage: GAUGE ,description: "Synchronous commit enabled, 1 if enabled, 0 if disabled" } #==============================================================# # 0230 pg_downstream #==============================================================# pg_downstream: name: pg_downstream desc: PostgreSQL replication client count (no state column on 9.1) query: |- SELECT 'connected' AS state, count(*) AS count FROM pg_stat_replication; ttl: 10 min_version: 90100 max_version: 90200 tags: [ cluster ] metrics: - state: { usage: LABEL ,description: "Replication client state" } - count: { usage: GAUGE ,description: "Count of replication clients by state" } pg_downstream_92: name: pg_downstream desc: PostgreSQL replication client count (group by state) query: |- SELECT state, count(*) AS count FROM pg_stat_replication GROUP BY state; ttl: 10 min_version: 90200 tags: [ cluster ] metrics: - state: { usage: LABEL ,description: "Replication client state" } - count: { usage: GAUGE ,description: "Count of replication clients by state" } #==============================================================# # 0240 pg_slot #==============================================================# pg_slot_96: name: pg_slot desc: PostgreSQL replication slot metrics 9.6 (with active_pid, confirmed_flush_lsn) query: |- SELECT slot_name, slot_type, plugin, database AS datname, datoid, active_pid, active, FALSE AS temporary, xmin::TEXT::BIGINT AS xmin, catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn, confirm_lsn, current_lsn - restart_lsn AS retained_bytes FROM ( SELECT slot_name, slot_type, plugin, database, datoid, active_pid, active, xmin, catalog_xmin, (('x' || lpad(split_part(restart_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(restart_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS restart_lsn, (('x' || lpad(split_part(confirmed_flush_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(confirmed_flush_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS confirm_lsn, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS current_lsn FROM pg_replication_slots, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90600 max_version: 100000 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot (N/A on 9.x, always 0)" } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } pg_slot_95: name: pg_slot desc: PostgreSQL replication slot metrics 9.5 (no confirmed_flush_lsn) query: |- SELECT slot_name, slot_type, plugin, database AS datname, datoid, active_pid, active, FALSE AS temporary, xmin::TEXT::BIGINT AS xmin, catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn, confirm_lsn, current_lsn - restart_lsn AS retained_bytes FROM ( SELECT slot_name, slot_type, plugin, database, datoid, active_pid, active, xmin, catalog_xmin, (('x' || lpad(split_part(restart_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(restart_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS restart_lsn, 0::BIGINT AS confirm_lsn, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS current_lsn FROM pg_replication_slots, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90500 max_version: 90600 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot (N/A on 9.x, always 0)" } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "Confirmed flush lsn (N/A before 9.6, always 0)" } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } pg_slot_94: name: pg_slot desc: PostgreSQL replication slot metrics 9.4 (no active_pid, confirmed_flush_lsn) query: |- SELECT slot_name, slot_type, plugin, database AS datname, datoid, active_pid, active, FALSE AS temporary, xmin::TEXT::BIGINT AS xmin, catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn, confirm_lsn, current_lsn - restart_lsn AS retained_bytes FROM ( SELECT slot_name, slot_type, plugin, database, datoid, NULL::INT AS active_pid, active, xmin, catalog_xmin, (('x' || lpad(split_part(restart_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(restart_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS restart_lsn, 0::BIGINT AS confirm_lsn, (('x' || lpad(split_part(current.loc::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(current.loc::text, '/', 2), 8, '0'))::bit(32)::bigint) AS current_lsn FROM pg_replication_slots, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_xlog_replay_location() ELSE pg_current_xlog_location() END AS loc) current ) d; ttl: 10 min_version: 90400 max_version: 90500 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "Process ID is not available before 9.5 (NULL)" } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot (N/A on 9.x, always 0)" } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "Confirmed flush lsn (N/A before 9.6, always 0)" } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } #==============================================================# # 0250 pg_recv #==============================================================# pg_recv_96: name: pg_recv desc: PostgreSQL walreceiver metrics (9.6 - 12) query: |- SELECT (regexp_match(conninfo, '.*host=(\S+).*'))[1] AS sender_host, (regexp_match(conninfo, '.*port=(\S+).*'))[1] AS sender_port, coalesce(slot_name, 'NULL') AS slot_name, pid, CASE status WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, (('x' || lpad(split_part(receive_start_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(receive_start_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS init_lsn, receive_start_tli AS init_tli, (('x' || lpad(split_part(received_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(received_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS flush_lsn, received_tli AS flush_tli, (('x' || lpad(split_part(latest_end_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(latest_end_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS reported_lsn, last_msg_send_time AS msg_send_time, last_msg_receipt_time AS msg_recv_time, latest_end_time AS reported_time, now() AS time FROM pg_stat_wal_receiver; ttl: 10 min_version: 90600 max_version: 130000 tags: [ cluster, replica ] metrics: - sender_host: { usage: LABEL ,description: "Host of the PostgreSQL instance this WAL receiver is connected to" } - sender_port: { usage: LABEL ,description: "Port number of the PostgreSQL instance this WAL receiver is connected to." } - slot_name: { usage: LABEL ,description: "Replication slot name used by this WAL receiver" } - pid: { usage: GAUGE ,description: "Process ID of the WAL receiver process" } - state: { usage: GAUGE ,description: "Encoded activity status of the WAL receiver process 0-4 for streaming|startup|catchup|backup|stopping" } - init_lsn: { usage: COUNTER ,description: "First write-ahead log location used when WAL receiver is started" } - init_tli: { usage: COUNTER ,description: "First timeline number used when WAL receiver is started" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and flushed to disk" } - flush_tli: { usage: COUNTER ,description: "Timeline number of last write-ahead log location received and flushed to disk" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - time: { usage: GAUGE ,description: "Time of current snapshot" } #==============================================================# # 0270 pg_origin #==============================================================# # skip by default, require additional privilege setup # GRANT SELECT ON pg_replication_origin, pg_replication_origin_status TO pg_monitor; pg_origin: name: pg_origin desc: PostgreSQL replay state (approximate) for a certain origin query: |- SELECT roname, (('x' || lpad(split_part(remote_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(remote_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS remote_lsn, (('x' || lpad(split_part(local_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(local_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS local_lsn FROM pg_replication_origin o LEFT JOIN pg_replication_origin_status os ON o.roident = os.local_id; ttl: 10 min_version: 90500 skip: true tags: [ cluster ] metrics: - roname: { usage: LABEL ,description: "The external, user defined, name of a replication origin." } - remote_lsn: { usage: COUNTER ,description: "The origin node's LSN up to which data has been replicated." } - local_lsn: { usage: COUNTER ,description: "This node's LSN at which remote_lsn has been replicated." } #==============================================================# # 0310 pg_size #==============================================================# pg_size: name: pg_size desc: PostgreSQL database size (legacy 9.1-9.6) query: |- SELECT datname, pg_database_size(oid) AS bytes FROM pg_database; ttl: 60 timeout: 1 min_version: 90100 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Database name" } - bytes: { usage: GAUGE ,description: "Database size in bytes" } #==============================================================# # 0320 pg_archiver #==============================================================# pg_archiver: name: pg_archiver desc: PostgreSQL archiver process statistics query: |- SELECT archived_count AS finish_count,failed_count, extract(epoch FROM last_archived_time) AS finish_time, extract(epoch FROM last_failed_time) AS failed_time, extract(epoch FROM stats_reset) AS reset_time FROM pg_stat_archiver; ttl: 60 min_version: 90400 tags: [ cluster ] metrics: - finish_count: { usage: COUNTER ,description: "Number of WAL files that have been successfully archived" } - failed_count: { usage: COUNTER ,description: "Number of failed attempts for archiving WAL files" } - finish_time: { usage: GAUGE ,description: "Time of the last successful archive operation" } - failed_time: { usage: GAUGE ,description: "Time of the last failed archival operation" } - reset_time: { usage: GAUGE ,description: "Time at which archive statistics were last reset" } #==============================================================# # 0330 pg_bgwriter #==============================================================# # https://pgpedia.info/p/pg_stat_bgwriter.html pg_bgwriter_94: name: pg_bgwriter desc: "PostgreSQL background writer metrics (PG 9.4-16)" query: SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint, buffers_clean, buffers_backend, maxwritten_clean, buffers_backend_fsync, buffers_alloc, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 90400 max_version: 170000 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - checkpoint_sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_checkpoint: { usage: COUNTER ,description: "Number of buffers written during checkpoints" } - buffers_clean: { usage: COUNTER ,description: "Number of buffers written by the background writer" } - buffers_backend: { usage: COUNTER ,description: "Number of buffers written directly by a backend" } - maxwritten_clean: { usage: COUNTER ,description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers" } - buffers_backend_fsync: { usage: COUNTER ,description: "Number of times a backend had to execute its own fsync call" } - buffers_alloc: { usage: COUNTER ,description: "Number of buffers allocated" } - reset_time: { usage: GAUGE ,description: "Time at which bgwriter statistics were last reset" } pg_bgwriter_91: name: pg_bgwriter desc: "PostgreSQL background writer metrics (PG 9.1-9.3)" query: SELECT checkpoints_timed, checkpoints_req, 0::BIGINT AS checkpoint_write_time, 0::BIGINT AS checkpoint_sync_time, buffers_checkpoint, buffers_clean, buffers_backend, maxwritten_clean, buffers_backend_fsync, buffers_alloc, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 90100 max_version: 90400 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time spent writing checkpoint files, in seconds (N/A on 9.1-9.3, always 0)" } - checkpoint_sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time spent syncing checkpoint files, in seconds (N/A on 9.1-9.3, always 0)" } - buffers_checkpoint: { usage: COUNTER ,description: "Number of buffers written during checkpoints" } - buffers_clean: { usage: COUNTER ,description: "Number of buffers written by the background writer" } - buffers_backend: { usage: COUNTER ,description: "Number of buffers written directly by a backend" } - maxwritten_clean: { usage: COUNTER ,description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers" } - buffers_backend_fsync: { usage: COUNTER ,description: "Number of times a backend had to execute its own fsync call" } - buffers_alloc: { usage: COUNTER ,description: "Number of buffers allocated" } - reset_time: { usage: GAUGE ,description: "Time at which bgwriter statistics were last reset" } #==============================================================# # 0331 pg_checkpointer #==============================================================# pg_checkpointer_94: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 9.4-16" query: SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 90400 max_version: 170000 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,rename: write_time ,scale: 1e-3 ,description: "Total amount of time that has been spent writing checkpoint files, in seconds" } - checkpoint_sync_time: { usage: COUNTER ,rename: sync_time ,scale: 1e-3 ,description: "Total amount of time that has been spent synchronizing checkpoint files to disk, in seconds" } - buffers_checkpoint: { usage: COUNTER ,rename: buffers_written ,description: "Number of buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } pg_checkpointer_91: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 9.1-9.3" query: SELECT checkpoints_timed, checkpoints_req, 0::BIGINT AS checkpoint_write_time, 0::BIGINT AS checkpoint_sync_time, buffers_checkpoint, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 90100 max_version: 90400 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,rename: write_time ,scale: 1e-3 ,description: "Total amount of time that has been spent writing checkpoint files, in seconds (N/A on 9.1-9.3, always 0)" } - checkpoint_sync_time: { usage: COUNTER ,rename: sync_time ,scale: 1e-3 ,description: "Total amount of time that has been spent synchronizing checkpoint files to disk, in seconds (N/A on 9.1-9.3, always 0)" } - buffers_checkpoint: { usage: COUNTER ,rename: buffers_written ,description: "Number of buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } #==============================================================# # 0340 pg_ssl #==============================================================# pg_ssl: name: pg_ssl desc: PostgreSQL SSL client connection count query: | SELECT count(*) FILTER (WHERE ssl) AS enabled, count(*) FILTER ( WHERE NOT ssl) AS disabled FROM pg_stat_ssl; ttl: 10 min_version: 90500 tags: [ cluster ] metrics: - enabled: { usage: GAUGE ,description: "Number of client connection that use ssl" } - disabled: { usage: GAUGE ,description: "Number of client connection that does not use ssl" } #==============================================================# # 0350 pg_checkpoint #==============================================================# pg_checkpoint: name: pg_checkpoint desc: checkpoint information from pg_control_checkpoint (9.6) query: |- SELECT (('x' || lpad(split_part(checkpoint_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(checkpoint_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS checkpoint_lsn, (('x' || lpad(split_part(redo_location::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(redo_location::text, '/', 2), 8, '0'))::bit(32)::bigint) AS redo_lsn, timeline_id AS tli, prev_timeline_id AS prev_tli, full_page_writes, split_part(next_xid, ':', 1) AS next_xid_epoch, split_part(next_xid, ':', 2) AS next_xid, next_oid::BIGINT, next_multixact_id::text::BIGINT, next_multi_offset::text::BIGINT, oldest_xid::text::BIGINT, oldest_xid_dbid::text::BIGINT, oldest_active_xid::text::BIGINT, oldest_multi_xid::text::BIGINT, oldest_multi_dbid::BIGINT, oldest_commit_ts_xid::text::BIGINT, newest_commit_ts_xid::text::BIGINT, checkpoint_time AS time, extract(epoch from now() - checkpoint_time) AS elapse FROM pg_control_checkpoint(); ttl: 60 min_version: 90600 tags: [ cluster ] metrics: - checkpoint_lsn: { usage: COUNTER ,description: "Latest checkpoint location" } - redo_lsn: { usage: COUNTER ,description: "Latest checkpoint's REDO location" } - tli: { usage: COUNTER ,description: "Latest checkpoint's TimeLineID" } - prev_tli: { usage: COUNTER ,description: "Latest checkpoint's PrevTimeLineID" } - full_page_writes: { usage: GAUGE ,description: "Latest checkpoint's full_page_writes enabled" } - next_xid_epoch: { usage: COUNTER ,description: "Latest checkpoint's NextXID epoch" } - next_xid: { usage: COUNTER ,description: "Latest checkpoint's NextXID xid" } - next_oid: { usage: COUNTER ,description: "Latest checkpoint's NextOID" } - next_multixact_id: { usage: COUNTER ,description: "Latest checkpoint's NextMultiXactId" } - next_multi_offset: { usage: COUNTER ,description: "Latest checkpoint's NextMultiOffset" } - oldest_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestXID" } - oldest_xid_dbid: { usage: GAUGE ,description: "Latest checkpoint's oldestXID's DB OID" } - oldest_active_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestActiveXID" } - oldest_multi_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestMultiXid" } - oldest_multi_dbid: { usage: GAUGE ,description: "Latest checkpoint's oldestMulti's DB OID" } - oldest_commit_ts_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestCommitTsXid" } - newest_commit_ts_xid: { usage: COUNTER ,description: "Latest checkpoint's newestCommitTsXid" } - time: { usage: COUNTER ,description: "Time of latest checkpoint" } - elapse: { usage: GAUGE ,description: "Seconds elapsed since latest checkpoint in seconds" } #==============================================================# # 0355 pg_timeline #==============================================================# pg_timeline: name: pg_timeline desc: Current timeline ID from primary or replica query: | SELECT COALESCE( (SELECT received_tli FROM pg_stat_wal_receiver), (SELECT timeline_id FROM pg_control_checkpoint()) ) AS id; ttl: 10 min_version: 90600 tags: [ cluster ] metrics: - id: { usage: GAUGE ,description: "Current timeline ID" } #==============================================================# # 0360 pg_recovery #==============================================================# pg_recovery: name: pg_recovery desc: PostgreSQL control recovery metrics (9.6) query: | SELECT min_recovery_end_timeline AS min_timeline, (('x' || lpad(split_part(min_recovery_end_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(min_recovery_end_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS min_lsn, (('x' || lpad(split_part(backup_start_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(backup_start_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS backup_start_lsn, (('x' || lpad(split_part(backup_end_lsn::text, '/', 1), 8, '0'))::bit(32)::bigint * 4294967296 + ('x' || lpad(split_part(backup_end_lsn::text, '/', 2), 8, '0'))::bit(32)::bigint) AS backup_end_lsn, end_of_backup_record_required AS require_record FROM pg_control_recovery(); ttl: 10 min_version: 90600 tags: [ cluster, replica ] metrics: - min_timeline: { usage: COUNTER ,description: "Min recovery ending loc's timeline" } - min_lsn: { usage: COUNTER ,description: "Minimum recovery ending location" } - backup_start_lsn: { usage: COUNTER ,description: "Backup start location" } - backup_end_lsn: { usage: COUNTER ,description: "Backup end location" } - require_record: { usage: GAUGE ,description: "End-of-backup record required" } #==============================================================# # 0410 pg_activity #==============================================================# pg_activity_92: name: pg_activity desc: PostgreSQL backend activity group by database and state (9.2+) query: |- SELECT datname, state, coalesce(count, 0) AS count, coalesce(max_duration, 0) AS max_duration, coalesce(max_tx_duration, 0) AS max_tx_duration, coalesce(max_conn_duration, 0) AS max_conn_duration FROM (SELECT d.datname, a.state FROM pg_database d, unnest(ARRAY ['active','idle','idle in transaction','idle in transaction (aborted)','fastpath function call','disabled']) a(state) WHERE d.datallowconn AND NOT d.datistemplate) base LEFT JOIN (SELECT datname, state, count(*) AS count, max(extract(epoch from now() - state_change)) AS max_duration, max(extract(epoch from now() - xact_start)) AS max_tx_duration, max(extract(epoch from now() - backend_start)) AS max_conn_duration FROM pg_stat_activity WHERE pid <> pg_backend_pid() GROUP BY 1,2) data USING (datname,state); ttl: 10 min_version: 90200 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - state: { usage: LABEL ,description: "Current overall state of this backend." } - count: { usage: GAUGE ,description: "Count of connection among (datname,state)" } - max_duration: { usage: GAUGE ,description: "Max duration since last state change among (datname, state)" } - max_tx_duration: { usage: GAUGE ,description: "Max transaction duration since state change among (datname, state)" } - max_conn_duration: { usage: GAUGE ,description: "Max backend session duration since state change among (datname, state)" } pg_activity_91: name: pg_activity desc: PostgreSQL backend activity group by database (9.1) query: | SELECT datname, 'active' AS state, count(*) AS count, max(extract(epoch from now() - query_start)) AS max_duration, max(extract(epoch from now() - xact_start)) AS max_tx_duration, max(extract(epoch from now() - backend_start)) AS max_conn_duration FROM pg_stat_activity WHERE procpid <> pg_backend_pid() AND datname IS NOT NULL GROUP BY datname; ttl: 10 min_version: 90100 max_version: 90200 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - state: { usage: LABEL ,description: "Current overall state of this backend (always active on 9.1)" } - count: { usage: GAUGE ,description: "Count of connection among (datname,state)" } - max_duration: { usage: GAUGE ,description: "Max duration since query start among (datname)" } - max_tx_duration: { usage: GAUGE ,description: "Max transaction duration among (datname)" } - max_conn_duration: { usage: GAUGE ,description: "Max backend session duration among (datname)" } #==============================================================# # 0420 pg_wait #==============================================================# pg_wait_96: name: pg_wait desc: PostgreSQL backend client count group by wait event type (9.6) query: | SELECT coalesce(datname, '_system') AS datname, coalesce(wait_event_type, 'Running') AS event, count(*) AS count FROM pg_stat_activity GROUP BY 1, 2; ttl: 10 min_version: 90600 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database, _system for global process" } - event: { usage: LABEL ,description: "Wait event type" } - count: { usage: GAUGE ,description: "Count of WaitEvent on target database" } pg_wait_91: name: pg_wait desc: PostgreSQL backend client count group by waiting flag (9.1-9.5) query: | SELECT coalesce(datname, '_system') AS datname, CASE WHEN waiting THEN 'Waiting' ELSE 'Running' END AS event, count(*) AS count FROM pg_stat_activity GROUP BY 1, 2; ttl: 10 min_version: 90100 max_version: 90600 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database, _system for global process" } - event: { usage: LABEL ,description: "Waiting flag, Waiting or Running" } - count: { usage: GAUGE ,description: "Backend count group by waiting flag" } #==============================================================# # 0440 pg_xact #==============================================================# pg_xact: name: pg_xact desc: PostgreSQL transaction identifier metrics query: | WITH snap(v) AS (SELECT txid_current_snapshot()), xset(v) AS (SELECT txid_snapshot_xip(v) FROM snap), xnum(v) AS (SELECT count(*) FROM xset), xmin(v) AS (SELECT txid_snapshot_xmin(v) FROM snap), xmax(v) AS (SELECT txid_snapshot_xmax(v) FROM snap) SELECT xmin.v AS xmin, xmax.v AS xmax, xnum.v AS xnum FROM xmin, xmax, xnum; ttl: 10 min_version: 90100 tags: [ cluster ] metrics: - xmin: { usage: COUNTER ,description: "Earliest txid that is still active" } - xmax: { usage: COUNTER ,description: "First as-yet-unassigned txid" } - xnum: { usage: GAUGE ,description: "Current active transaction count" } #==============================================================# # 0450 pg_lock #==============================================================# pg_lock: name: pg_lock desc: PostgreSQL lock distribution by mode and database query: | SELECT datname, mode, coalesce(count, 0) AS count FROM (SELECT d.oid AS database, d.datname, l.mode FROM pg_database d, unnest(ARRAY ['AccessShareLock','RowShareLock','RowExclusiveLock','ShareUpdateExclusiveLock', 'ShareLock','ShareRowExclusiveLock','ExclusiveLock','AccessExclusiveLock']) l(mode) WHERE d.datallowconn AND NOT d.datistemplate) base LEFT JOIN (SELECT database, mode, count(*) AS count FROM pg_locks WHERE database IS NOT NULL GROUP BY 1, 2) cnt USING (database, mode); ttl: 10 min_version: 90100 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - mode: { usage: LABEL ,description: "Name of the lock mode held or desired by this process" } - count: { usage: GAUGE ,description: "Number of locks of corresponding mode and database" } #==============================================================# # 0460 pg_query #==============================================================# pg_query_94: name: pg_query desc: PostgreSQL query statement metrics, require pg_stat_statements installed, 9.4 - 12 query: |- SELECT datname, queryid AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_time) AS exec_time, sum(blk_read_time) + sum(blk_write_time) AS io_time, sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, sum(shared_blks_dirtied) AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements(false) s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 90400 max_version: 130000 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "QueryID generated from internal hash code, computed from the statement's parse tree" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } pg_query_91: name: pg_query desc: PostgreSQL query statement metrics, require pg_stat_statements installed, 9.1 - 9.3 (no queryid) query: |- SELECT datname, md5(query) AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_time) AS exec_time, 0::FLOAT AS io_time, sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, 0::BIGINT AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 90100 max_version: 90400 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "MD5 hash of query text (no queryid before 9.4)" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds (N/A before 9.4, always 0)" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement (N/A before 9.4, always 0)" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } #==============================================================# # 0610 pg_db #==============================================================# pg_db_92: name: pg_db desc: PostgreSQL database stats from pg_stat_database (9.2 - 9.6) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total, blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,blk_read_time,blk_write_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 90200 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_db_91: name: pg_db desc: PostgreSQL database stats from pg_stat_database (9.1, fewer columns) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total, blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts, 0::BIGINT AS temp_files, 0::BIGINT AS temp_bytes, 0::BIGINT AS deadlocks, 0::BIGINT AS blk_read_time, 0::BIGINT AS blk_write_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 90100 max_version: 90200 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database (N/A on 9.1, always 0)" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database (N/A on 9.1, always 0)" } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database (N/A on 9.1, always 0)" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds (N/A on 9.1, always 0)" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds (N/A on 9.1, always 0)" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } #==============================================================# # 0620 pg_db_confl #==============================================================# # https://pgpedia.info/p/pg_stat_database_conflicts.html pg_db_confl: name: pg_db_confl desc: PostgreSQL database conflicts metrics for pg 9.1 - 9.6 query: SELECT datid,datname,confl_tablespace,confl_lock,confl_snapshot,confl_bufferpin,confl_deadlock FROM pg_stat_database_conflicts; ttl: 10 min_version: 90100 tags: [ cluster, replica ] metrics: - datid: { usage: DISCARD } - datname: { usage: LABEL ,description: "Name of this database" } - confl_tablespace: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to dropped tablespaces" } - confl_lock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to lock timeouts" } - confl_snapshot: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to old snapshots" } - confl_bufferpin: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to pinned buffers" } - confl_deadlock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to deadlocks" } #==============================================================# # 0700 pg_table #==============================================================# pg_table_94: name: pg_table desc: PostgreSQL table metrics 9.4-9.6 query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 90400 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_table_91: name: pg_table desc: PostgreSQL table metrics 9.1-9.3 (no n_mod_since_analyze) query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_live_tup,psut.n_dead_tup, NULL::BIGINT AS n_mod_since_analyze, psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 90100 max_version: 90400 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,t/toast/116" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed (N/A on 9.1-9.3, NULL)" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } #==============================================================# # 0710 pg_index #==============================================================# pg_index: name: pg_index desc: PostgreSQL index metrics (legacy 9.1-9.6) query: |- SELECT CURRENT_CATALOG AS datname, psui.schemaname || '.' || psui.indexrelname AS idxname, psui.schemaname || '.' || psui.relname AS relname, psui.indexrelid AS relid, c.relpages, c.reltuples, psui.idx_scan, psui.idx_tup_read, psui.idx_tup_fetch, psio.idx_blks_read, psio.idx_blks_hit FROM pg_stat_user_indexes psui JOIN pg_statio_user_indexes psio ON psio.indexrelid = psui.indexrelid JOIN pg_class c ON c.oid = psui.indexrelid WHERE psui.schemaname !~ '^pg_' AND psui.schemaname !~ '^_' AND psui.schemaname !~ '^timescaledb' AND psui.schemaname !~ '^citus' AND psui.schemaname !~ '^columnar' AND psui.schemaname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') ORDER BY psui.idx_tup_read DESC LIMIT 512; ttl: 10 timeout: 1 min_version: 90100 metrics: - datname: { usage: LABEL ,description: "Database name of this index" } - idxname: { usage: LABEL ,description: "Name of this index (full-qualified schema name)" } - relname: { usage: LABEL ,description: "Name of the table for this index (full-qualified schema name)" } - relid: { usage: LABEL ,description: "Relation oid of this index" } - relpages: { usage: GAUGE ,description: "Size of the on-disk representation of this index in pages" } - reltuples: { usage: GAUGE ,description: "Estimate relation tuples" } - idx_scan: { usage: COUNTER ,description: "Number of index scans initiated on this index" } - idx_tup_read: { usage: COUNTER ,description: "Number of index entries returned by scans on this index" } - idx_tup_fetch: { usage: COUNTER ,description: "Number of live table rows fetched by simple index scans using this index" } - idx_blks_read: { usage: COUNTER ,description: "Number of disk blocks read from this index" } - idx_blks_hit: { usage: COUNTER ,description: "Number of buffer hits in this index" } #==============================================================# # 0720 pg_func #==============================================================# pg_func: desc: PostgreSQL function metrics query: SELECT CURRENT_CATALOG AS datname, schemaname || '.' || funcname AS funcname, sum(calls) AS calls, sum(total_time) AS total_time, sum(self_time) AS self_time FROM pg_stat_user_functions GROUP BY 2 ORDER BY 4 DESC LIMIT 128; ttl: 10 min_version: 90100 metrics: - datname: { usage: LABEL ,description: "Name of belonged database" } - funcname: { usage: LABEL ,description: "Name of this function, may have multiple override" } - calls: { usage: COUNTER ,description: "Number of times this function has been called" } - total_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent in this function and all other functions called by it, in seconds" } - self_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent in this function itself, not including other functions called by it, in seconds" } #==============================================================# # 0740 pg_relkind #==============================================================# pg_relkind: name: pg_relkind desc: Postgres relation count by kind query: | SELECT CURRENT_CATALOG AS datname, relkind, count(*) AS count FROM pg_class GROUP BY relkind; ttl: 60 timeout: 1 min_version: 90100 metrics: - datname: { usage: LABEL ,description: "Database name" } - relkind: { usage: LABEL ,description: "Relation kind (r,i,S,t,v,c,...)" } - count: { usage: GAUGE ,description: "Number of relations" } #==============================================================# # 0810 pg_table_size #==============================================================# pg_table_size: desc: PostgreSQL table size metrics, quite slow query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || rel.relname AS relname, pg_total_relation_size(rel.oid) AS bytes, pg_relation_size(rel.oid) AS relsize, pg_indexes_size(rel.oid) AS indexsize, pg_total_relation_size(reltoastrelid) AS toastsize FROM pg_namespace nsp JOIN pg_class rel ON nsp.oid = rel.relnamespace WHERE nspname <> ALL(ARRAY['pg_catalog', 'information_schema']) AND rel.relkind = 'r' ORDER BY 3 DESC NULLS LAST LIMIT 256; ttl: 300 timeout: 2 min_version: 90100 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Schema qualified table name" } - bytes: { usage: GAUGE ,default: 0 ,description: "Total bytes of this table (including toast, index, toast index)" } - relsize: { usage: GAUGE ,default: 0 ,description: "Bytes of this table itself (main, vm, fsm)" } - indexsize: { usage: GAUGE ,default: 0 ,description: "Bytes of all related indexes of this table" } - toastsize: { usage: GAUGE ,default: 0 ,description: "Bytes of toast tables of this table" } #==============================================================# # 0820 pg_table_bloat #==============================================================# # pg_table_bloat require auxiliary view to work. Disable it or create auxiliary view before use: pg_table_bloat: name: pg_table_bloat desc: PostgreSQL table bloat metrics, require auxiliary view pg_table_bloat to work query: SELECT datname, nspname || '.' || relname AS relname, size, ratio FROM pg_table_bloat ORDER BY size DESC LIMIT 64; ttl: 300 timeout: 2 min_version: 90400 skip: true metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Schema qualified name of this table" } - size: { usage: GAUGE ,description: "Total bytes of this table" } - ratio: { usage: GAUGE ,description: "Estimated bloat ratio of this table from 0 to 1" } #==============================================================# # 0830 pg_index_bloat #==============================================================# # pg_index_bloat require auxiliary view to work. Disable it or create auxiliary view before use: pg_index_bloat: name: pg_index_bloat desc: PostgreSQL index bloat metrics, require auxiliary view pg_index_bloat to work query: SELECT datname, nspname || '.' || idxname AS idxname, size, ratio FROM pg_index_bloat ORDER BY size DESC LIMIT 64; ttl: 300 timeout: 2 min_version: 90400 skip: true metrics: - datname: { usage: LABEL ,description: "Database name of this index" } - idxname: { usage: LABEL ,description: "Schema qualified name of this index" } - size: { usage: GAUGE ,description: "Total bytes of this index" } - ratio: { usage: GAUGE ,description: "Estimated bloat ratio of this index from 0 to 1" } #==============================================================# # 0910 pgbouncer_list #==============================================================# # http://www.pgbouncer.org/usage.html#show-lists pgbouncer_list: name: pgbouncer_list desc: Pgbouncer entry list query: SHOW LISTS; ttl: 10 min_version: 10800 fatal: true tags: [ pgbouncer ] metrics: - list: { usage: LABEL ,description: "Pgbouncer internal list name" } - items: { usage: GAUGE ,description: "Number of corresponding pgbouncer object" } #==============================================================# # 0920 pgbouncer_database #==============================================================# # http://www.pgbouncer.org/usage.html#show-databases pgbouncer_database_124: name: pgbouncer_database desc: Pgbouncer database stats (since 1.24) query: SHOW DATABASES; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool_size: { usage: GAUGE ,rename: reserve_pool ,description: "Maximum number of additional connections for this database" } - server_lifetime: { usage: GAUGE ,description: "The maximum lifetime of a server connection for this database" } - pool_mode: { usage: DISCARD } - load_balance_hosts: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - max_client_connections: { usage: GAUGE ,description: "Maximum number of allowed client connections for this pgbouncer instance" } - current_client_connections: { usage: GAUGE ,description: "Current number of client connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_123: name: pgbouncer_database desc: Pgbouncer database stats 1.23 query: SHOW DATABASES; ttl: 10 min_version: 12300 max_version: 12400 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - server_lifetime: { usage: GAUGE ,description: "The maximum lifetime of a server connection for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_116: name: pgbouncer_database desc: Pgbouncer database stats (1.16-1.22) query: SHOW DATABASES; ttl: 10 min_version: 11600 max_version: 12300 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_108: name: pgbouncer_database desc: Pgbouncer database stats (1.08-1.15) query: SHOW DATABASES; ttl: 10 min_version: 10800 max_version: 11600 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } #==============================================================# # 0930 pgbouncer_stat #==============================================================# # http://www.pgbouncer.org/usage.html#show-stats pgbouncer_stat_124: name: pgbouncer_stat desc: Pgbouncer stats per database (since 1.24) query: SHOW STATS; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_server_assignment_count: { usage: COUNTER ,description: "Total times a server was assigned to a client" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - total_client_parse_count: { usage: COUNTER ,description: "Total number of prepared statements created by clients" } - total_server_parse_count: { usage: COUNTER ,description: "Total number of prepared statements created on a server." } - total_bind_count: { usage: COUNTER ,description: "Total number of prepared statements readied for execution by clients and forwarded to postgres" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_server_assignment_count: { usage: GAUGE ,description: "Average number of times a server as assigned to a client per second in the last stat period." } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } - avg_client_parse_count: { usage: GAUGE ,description: "Average number of prepared statements created by clients" } - avg_server_parse_count: { usage: GAUGE ,description: "Average number of prepared statements created on a server." } - avg_bind_count: { usage: GAUGE ,description: "Average number of prepared statements readied for execution by clients and forwarded to postgres" } pgbouncer_stat_123: name: pgbouncer_stat desc: Pgbouncer stats per database (1.23) query: SHOW STATS; ttl: 10 min_version: 12300 max_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_server_assignment_count: { usage: COUNTER ,description: "Total times a server was assigned to a client" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_server_assignment_count: { usage: GAUGE ,description: "Average number of times a server as assigned to a client per second in the last stat period." } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } pgbouncer_stat_108: name: pgbouncer_stat desc: Pgbouncer stats per database (1.08 - 1.22) query: SHOW STATS; ttl: 10 min_version: 10800 max_version: 12300 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } #==============================================================# # 0940 pgbouncer_pool #==============================================================# # http://www.pgbouncer.org/usage.html#show-pools pgbouncer_pool_124: name: pgbouncer_pool desc: Pgbouncer pool stats (1.24+) query: SHOW POOLS; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_active_cancel_req: { usage: GAUGE, rename: active_cancel_clients, description: "Client connections that have forwarded query cancellations to the server and are waiting for the server response." } - cl_waiting_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_active_cancel: { usage: GAUGE, rename: active_cancel_servers, description: "Server connections that are currently forwarding a cancel request" } - sv_being_canceled: { usage: GAUGE, rename: cancel_servers, description: "cancel requests have completed that were sent to cancel a query on this server" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } - load_balance_hosts: { usage: LABEL, description: "The load_balance_hosts in use" } pgbouncer_pool_118: name: pgbouncer_pool desc: Pgbouncer pool stats (1.18-1.23) query: SHOW POOLS; ttl: 10 min_version: 11800 max_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_active_cancel_req: { usage: GAUGE, rename: active_cancel_clients, description: "Client connections that have forwarded query cancellations to the server and are waiting for the server response." } - cl_waiting_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_active_cancel: { usage: GAUGE, rename: active_cancel_servers, description: "Server connections that are currently forwarding a cancel request" } - sv_being_canceled: { usage: GAUGE, rename: cancel_servers, description: "cancel requests have completed that were sent to cancel a query on this server" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pgbouncer_pool_116: name: pgbouncer_pool desc: Pgbouncer pool stats (1.16-1.17) query: SHOW POOLS; ttl: 10 min_version: 11600 max_version: 11800 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pgbouncer_pool_108: name: pgbouncer_pool desc: Pgbouncer pool stats (1.08-1.15) query: SHOW POOLS; ttl: 10 min_version: 10800 max_version: 11600 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } #==============================================================# # 1800 pg_tsdb_hypertable #==============================================================# # this collector reqires timescaledb extension to be installed pg_tsdb_hypertable: name: pg_tsdb_hypertable desc: TimescaleDB hypertable overview query: |- SELECT current_database() AS datname, format('%I.%I', hypertable_schema, hypertable_name) AS relname, num_dimensions AS dimensions, num_chunks AS chunks, compression_enabled::BOOLEAN::int AS compressed, hypertable_size(format('"%I"."%I"', hypertable_schema, hypertable_name)::RegClass) AS bytes FROM timescaledb_information.hypertables; ttl: 60 timeout: 2 min_version: 90600 skip: true tags: [ "extension:timescaledb", "schema:timescaledb_information" ] metrics: - datname: { usage: LABEL ,description: "database name" } - relname: { usage: LABEL ,description: "Hypertable relation name" } - dimensions: { usage: GAUGE ,description: "Number of partitioning dimensions" } - chunks: { usage: GAUGE ,description: "Total chunks of this hypertable" } - compressed: { usage: GAUGE ,description: "1 if compression enabled" } - bytes: { usage: GAUGE ,description: "Total size of hypertable in bytes" } #==============================================================# # 1900 pg_citus_node #==============================================================# # https://docs.citusdata.com/en/latest/develop/api_metadata.html#worker-node-table pg_citus_node: name: pg_citus_node desc: Citus worker coordinator node inventory query: |- SELECT CONCAT(nodename, ':', nodeport) AS node, current_database() AS datname, nodeid AS id, groupid AS group, hasmetadata::BOOLEAN::INT AS has_meta, isactive::BOOLEAN::INT AS is_active, metadatasynced::BOOLEAN::INT AS meta_synced, shouldhaveshards::BOOLEAN::INT AS have_shards FROM pg_dist_node; ttl: 60 min_version: 90600 tags: [ "extension:citus" ] metrics: - node: { usage: LABEL ,description: "nodename:port of the PostgreSQL instance" } - datname: { usage: LABEL ,description: "database name" } - id: { usage: GAUGE ,description: "auto‑generated node identifier" } - group: { usage: GAUGE ,description: "replication group id (primary + secondaries)" } - has_meta: { usage: GAUGE ,description: "1 = internal use flag set" } - is_active: { usage: GAUGE ,description: "1 = node currently accepts shards" } - meta_synced: { usage: GAUGE ,description: "1 = metadata fully synced to node" } - have_shards: { usage: GAUGE ,description: "1 = rebalancer may place shards here" } #==============================================================# # 2000 heartbeat #==============================================================# # this is a example of application monitoring and predicate queries pg_heartbeat: name: pg_heartbeat desc: monitoring heartbeat in monitor.heartbeat table predicate_queries: - name: if heartbeat table exists predicate_query: | SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'monitor' AND table_name = 'heartbeat'); query: |- SELECT id AS cluster_name, extract(EPOCH FROM ts) AS ts, lsn, txid FROM monitor.heartbeat; ttl: 10 min_version: 90100 tags: [ "dbname:postgres", "schema:monitor" ] skip: true metrics: - cluster_name: { usage: LABEL ,description: "cluster_name param of this database cluster" } - ts: { usage: GAUGE ,description: "unix timestamp of the heartbeat" } - lsn: { usage: COUNTER ,description: "lsn of the heartbeat" } - txid: { usage: GAUGE ,description: "txid of the heartbeat" } pg_exporter-1.4.1/main.go000066400000000000000000000015511523237665500153460ustar00rootroot00000000000000/***********************************************************************\ Copyright © 2018-2026 Ruohang Feng Contributors: https://github.com/pgsty/pg_exporter/graphs/contributors 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 "pg_exporter/exporter" func main() { exporter.Run() } pg_exporter-1.4.1/monitor/000077500000000000000000000000001523237665500155605ustar00rootroot00000000000000pg_exporter-1.4.1/monitor/initdb.sh000066400000000000000000000514451523237665500173760ustar00rootroot00000000000000#!/bin/bash set -euo pipefail #==============================================================# # File : initdb.sh # Mtime : 2020-09-02 # Desc : initdb.sh # Path : /pg/bin/initdb.sh # Depend : CentOS 7 # Author : Vonng(rh@vonng.com) # Note : Run this as dbsu (postgres) #==============================================================# PROG_NAME="$(basename $0))" PROG_DIR="$(cd $(dirname $0) && pwd)" #--------------------------------------------------------------------------- function log() { printf "[$(date "+%Y-%m-%d %H:%M:%S")][$HOSTNAME][INITDB] $*\n" >> /pg/log/initdb.log } #--------------------------------------------------------------------------- #---------------------------------------------------------------------------- # template variables #---------------------------------------------------------------------------- PG_DBSU='{{ pg_dbsu }}' PG_REPLICATION_USERNAME='{{ pg_replication_username }}' PG_REPLICATION_PASSWORD='{{ pg_replication_password }}' PG_MONITOR_USERNAME='{{ pg_monitor_username }}' PG_MONITOR_PASSWORD='{{ pg_monitor_password }}' PG_DEFAULT_USERNAME='{{ pg_default_username }}' PG_DEFAULT_PASSWORD='{{ pg_default_password }}' PG_DEFAULT_DATABASE='{{ pg_default_database }}' PG_DEFAULT_SCHEMA='{{ pg_default_schema }}' PG_DEFAULT_EXTENSIONS='{{ pg_default_extensions }}' #---------------------------------------------------------------------------- # system users #---------------------------------------------------------------------------- log "initdb: create system users: ${PG_REPLICATION_USERNAME} , ${PG_MONITOR_USERNAME}" psql -AXtwq postgres <<- EOF -- dbsu CREATE USER ${PG_DBSU}; ALTER USER "${PG_DBSU}" SUPERUSER; COMMENT ON ROLE "${PG_REPLICATION_USERNAME}" IS 'system default sa'; -- replication user (also used as rewind user) CREATE USER ${PG_REPLICATION_USERNAME}; COMMENT ON ROLE "${PG_REPLICATION_USERNAME}" IS 'system user for replication'; ALTER USER ${PG_REPLICATION_USERNAME} REPLICATION PASSWORD '${PG_REPLICATION_PASSWORD}'; GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean) TO "${PG_REPLICATION_USERNAME}"; GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) TO "${PG_REPLICATION_USERNAME}"; GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) TO "${PG_REPLICATION_USERNAME}"; GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean) TO "${PG_REPLICATION_USERNAME}"; -- system user: dbuser_monitor CREATE USER "${PG_MONITOR_USERNAME}"; COMMENT ON ROLE "${PG_MONITOR_USERNAME}" IS 'system user for monitor'; ALTER USER "${PG_MONITOR_USERNAME}" LOGIN NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOREPLICATION; ALTER USER "${PG_MONITOR_USERNAME}" PASSWORD '${PG_MONITOR_PASSWORD}' CONNECTION LIMIT 8; ALTER USER "${PG_MONITOR_USERNAME}" SET search_path = public,monitor; GRANT pg_monitor TO "${PG_MONITOR_USERNAME}"; EOF #---------------------------------------------------------------------------- # default roles #---------------------------------------------------------------------------- log "initdb: create default roles: dbrole_admin, dbrole_readwrite, dbrole_readonly" psql -AXtwq postgres <<- EOF -- default read-only role: personal account, analysis & etl purpose CREATE ROLE dbrole_readonly; -- analysis , personal account, etc... COMMENT ON ROLE dbrole_readonly IS 'read-only role, for personal, analysis, etl purpose'; ALTER ROLE dbrole_readonly NOLOGIN NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOREPLICATION NOBYPASSRLS; -- default read-write role: common production account CREATE ROLE dbrole_readwrite; -- common read-write, production account ALTER ROLE dbrole_readwrite NOLOGIN NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOREPLICATION NOBYPASSRLS; COMMENT ON ROLE dbrole_readwrite IS 'read-write role, common production account'; -- default admin role: create database,role,table, partition, index, etc... CREATE ROLE dbrole_admin; -- admin role, create db, role, table, partition, index, etc... COMMENT ON ROLE dbrole_admin IS 'admin role, create db, role, table, partition, index, etc...'; ALTER ROLE dbrole_admin NOLOGIN NOSUPERUSER INHERIT CREATEROLE CREATEDB NOREPLICATION BYPASSRLS; -- grant GRANT dbrole_readonly TO dbrole_readwrite; GRANT dbrole_readonly TO "${PG_MONITOR_USERNAME}"; -- since monitor user can only access from local or meta nodes GRANT dbrole_readwrite TO dbrole_admin; EOF #---------------------------------------------------------------------------- # default user (business account) #---------------------------------------------------------------------------- if [ ${PG_DEFAULT_USERNAME} != 'postgres' ]; then log "initdb: create default business user: ${PG_DEFAULT_USERNAME}" psql -AXtwq postgres <<- EOF -- default user CREATE USER "${PG_DEFAULT_USERNAME}"; COMMENT ON ROLE "${PG_DEFAULT_USERNAME}" IS 'default business user'; ALTER USER "${PG_DEFAULT_USERNAME}" LOGIN NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOREPLICATION BYPASSRLS; ALTER USER "${PG_DEFAULT_USERNAME}" PASSWORD '${PG_DEFAULT_PASSWORD}'; GRANT dbrole_readwrite TO "${PG_DEFAULT_USERNAME}"; EOF fi #---------------------------------------------------------------------------- # create pgpass #---------------------------------------------------------------------------- log "initdb: create pgpass file" echo "" >> ~/.pgpass function add_pgpass(){ local username=$1 local password=$2 if grep -q "${username}": ~/.pgpass; then sed -i "/${username}/d" ~/.pgpass fi echo '*:*:*'"${username}:${password}" >> ~/.pgpass chmod 0600 ~/.pgpass } add_pgpass ${PG_REPLICATION_USERNAME} ${PG_REPLICATION_PASSWORD} add_pgpass ${PG_MONITOR_USERNAME} ${PG_MONITOR_PASSWORD} if [[ ${PG_DEFAULT_USERNAME} != 'postgres' ]]; then add_pgpass ${PG_DEFAULT_USERNAME} ${PG_DEFAULT_PASSWORD} fi #---------------------------------------------------------------------------- # default privilege #---------------------------------------------------------------------------- log "initdb: alter default privilege: postgres template1" for database in postgres template1 do psql -AXtwq ${database} <<- EOF ALTER DEFAULT PRIVILEGES FOR ROLE dbrole_admin GRANT USAGE ON SCHEMAS TO dbrole_readonly; ALTER DEFAULT PRIVILEGES FOR ROLE dbrole_admin GRANT SELECT ON TABLES TO dbrole_readonly; ALTER DEFAULT PRIVILEGES FOR ROLE dbrole_admin GRANT SELECT ON SEQUENCES TO dbrole_readonly; ALTER DEFAULT PRIVILEGES FOR ROLE dbrole_admin GRANT EXECUTE ON FUNCTIONS TO dbrole_readonly; ALTER DEFAULT PRIVILEGES FOR ROLE dbrole_admin GRANT INSERT, UPDATE, DELETE ON TABLES TO dbrole_readwrite; ALTER DEFAULT PRIVILEGES FOR ROLE dbrole_admin GRANT USAGE, UPDATE ON SEQUENCES TO dbrole_readwrite; ALTER DEFAULT PRIVILEGES FOR ROLE dbrole_admin GRANT TRUNCATE, REFERENCES, TRIGGER ON TABLES TO dbrole_admin; ALTER DEFAULT PRIVILEGES FOR ROLE dbrole_admin GRANT CREATE ON SCHEMAS TO dbrole_admin; ALTER DEFAULT PRIVILEGES FOR ROLE dbrole_admin GRANT USAGE ON TYPES TO dbrole_admin; ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT USAGE ON SCHEMAS TO dbrole_readonly; ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT SELECT ON TABLES TO dbrole_readonly; ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT SELECT ON SEQUENCES TO dbrole_readonly; ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT EXECUTE ON FUNCTIONS TO dbrole_readonly; ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT INSERT, UPDATE, DELETE ON TABLES TO dbrole_readwrite; ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT USAGE, UPDATE ON SEQUENCES TO dbrole_readwrite; ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT TRUNCATE, REFERENCES, TRIGGER ON TABLES TO dbrole_admin; ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT CREATE ON SCHEMAS TO dbrole_admin; ALTER DEFAULT PRIVILEGES FOR ROLE postgres GRANT USAGE ON TYPES TO dbrole_admin; EOF done #---------------------------------------------------------------------------- # template database #---------------------------------------------------------------------------- log "initdb: init database template: postgres, template1" for database in postgres template1; do psql -AXtwq ${database} <<-EOF CREATE SCHEMA IF NOT EXISTS monitor; SET search_path = public, monitor; -- create stats extensions within monitor schema CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA monitor; CREATE EXTENSION IF NOT EXISTS pgstattuple WITH SCHEMA monitor; CREATE EXTENSION IF NOT EXISTS pg_qualstats WITH SCHEMA monitor; CREATE EXTENSION IF NOT EXISTS pg_buffercache WITH SCHEMA monitor; CREATE EXTENSION IF NOT EXISTS pageinspect WITH SCHEMA monitor; CREATE EXTENSION IF NOT EXISTS pg_prewarm WITH SCHEMA monitor; CREATE EXTENSION IF NOT EXISTS pg_visibility WITH SCHEMA monitor; CREATE EXTENSION IF NOT EXISTS pg_freespacemap WITH SCHEMA monitor; CREATE EXTENSION IF NOT EXISTS pg_repack WITH SCHEMA monitor; CREATE EXTENSION IF NOT EXISTS pg_stat_kcache WITH SCHEMA monitor; -- CREATE EXTENSION IF NOT EXISTS pg_cron WITH SCHEMA monitor; -- Table bloat estimate CREATE OR REPLACE VIEW monitor.pg_table_bloat AS SELECT CURRENT_CATALOG AS datname, nspname, relname , bs * tblpages AS size, CASE WHEN tblpages - est_tblpages_ff > 0 THEN (tblpages - est_tblpages_ff)/tblpages::FLOAT ELSE 0 END AS ratio FROM ( SELECT ceil( reltuples / ( (bs-page_hdr)*fillfactor/(tpl_size*100) ) ) + ceil( toasttuples / 4 ) AS est_tblpages_ff, tblpages, fillfactor, bs, tblid, nspname, relname, is_na FROM ( SELECT ( 4 + tpl_hdr_size + tpl_data_size + (2 * ma) - CASE WHEN tpl_hdr_size % ma = 0 THEN ma ELSE tpl_hdr_size % ma END - CASE WHEN ceil(tpl_data_size)::INT % ma = 0 THEN ma ELSE ceil(tpl_data_size)::INT % ma END ) AS tpl_size, (heappages + toastpages) AS tblpages, heappages, toastpages, reltuples, toasttuples, bs, page_hdr, tblid, nspname, relname, fillfactor, is_na FROM ( SELECT tbl.oid AS tblid, ns.nspname , tbl.relname, tbl.reltuples, tbl.relpages AS heappages, coalesce(toast.relpages, 0) AS toastpages, coalesce(toast.reltuples, 0) AS toasttuples, coalesce(substring(array_to_string(tbl.reloptions, ' ') FROM 'fillfactor=([0-9]+)')::smallint, 100) AS fillfactor, current_setting('block_size')::numeric AS bs, CASE WHEN version()~'mingw32' OR version()~'64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END AS ma, 24 AS page_hdr, 23 + CASE WHEN MAX(coalesce(s.null_frac,0)) > 0 THEN ( 7 + count(s.attname) ) / 8 ELSE 0::int END + CASE WHEN bool_or(att.attname = 'oid' and att.attnum < 0) THEN 4 ELSE 0 END AS tpl_hdr_size, sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 0) ) AS tpl_data_size, bool_or(att.atttypid = 'pg_catalog.name'::regtype) OR sum(CASE WHEN att.attnum > 0 THEN 1 ELSE 0 END) <> count(s.attname) AS is_na FROM pg_attribute AS att JOIN pg_class AS tbl ON att.attrelid = tbl.oid JOIN pg_namespace AS ns ON ns.oid = tbl.relnamespace LEFT JOIN pg_stats AS s ON s.schemaname=ns.nspname AND s.tablename = tbl.relname AND s.inherited=false AND s.attname=att.attname LEFT JOIN pg_class AS toast ON tbl.reltoastrelid = toast.oid WHERE NOT att.attisdropped AND tbl.relkind = 'r' AND nspname NOT IN ('pg_catalog','information_schema') GROUP BY 1,2,3,4,5,6,7,8,9,10 ) AS s ) AS s2 ) AS s3 WHERE NOT is_na; -- Index bloat estimate CREATE OR REPLACE VIEW monitor.pg_index_bloat AS SELECT CURRENT_CATALOG AS datname, nspname, idxname AS relname, relpages::BIGINT * bs AS size, COALESCE((relpages - ( reltuples * (6 + ma - (CASE WHEN index_tuple_hdr % ma = 0 THEN ma ELSE index_tuple_hdr % ma END) + nulldatawidth + ma - (CASE WHEN nulldatawidth % ma = 0 THEN ma ELSE nulldatawidth % ma END)) / (bs - pagehdr)::FLOAT + 1 )), 0) / relpages::FLOAT AS ratio FROM ( SELECT nspname, idxname, reltuples, relpages, current_setting('block_size')::INTEGER AS bs, (CASE WHEN version() ~ 'mingw32' OR version() ~ '64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END) AS ma, 24 AS pagehdr, (CASE WHEN max(COALESCE(pg_stats.null_frac, 0)) = 0 THEN 2 ELSE 6 END) AS index_tuple_hdr, sum((1.0 - COALESCE(pg_stats.null_frac, 0.0)) * COALESCE(pg_stats.avg_width, 1024))::INTEGER AS nulldatawidth FROM pg_attribute JOIN ( SELECT pg_namespace.nspname, ic.relname AS idxname, ic.reltuples, ic.relpages, pg_index.indrelid, pg_index.indexrelid, tc.relname AS tablename, regexp_split_to_table(pg_index.indkey::TEXT, ' ') :: INTEGER AS attnum, pg_index.indexrelid AS index_oid FROM pg_index JOIN pg_class ic ON pg_index.indexrelid = ic.oid JOIN pg_class tc ON pg_index.indrelid = tc.oid JOIN pg_namespace ON pg_namespace.oid = ic.relnamespace JOIN pg_am ON ic.relam = pg_am.oid WHERE pg_am.amname = 'btree' AND ic.relpages > 0 AND nspname NOT IN ('pg_catalog', 'information_schema') ) ind_atts ON pg_attribute.attrelid = ind_atts.indexrelid AND pg_attribute.attnum = ind_atts.attnum JOIN pg_stats ON pg_stats.schemaname = ind_atts.nspname AND ((pg_stats.tablename = ind_atts.tablename AND pg_stats.attname = pg_get_indexdef(pg_attribute.attrelid, pg_attribute.attnum, TRUE)) OR (pg_stats.tablename = ind_atts.idxname AND pg_stats.attname = pg_attribute.attname)) WHERE pg_attribute.attnum > 0 GROUP BY 1, 2, 3, 4, 5, 6 ) est LIMIT 512; -- index bloat overview CREATE OR REPLACE VIEW monitor.pg_table_bloat_human AS SELECT nspname || '.' || relname AS name, pg_size_pretty(size) AS size, pg_size_pretty((size * ratio)::BIGINT) AS wasted, round(100 * ratio::NUMERIC, 2) as ratio FROM monitor.pg_table_bloat ORDER BY wasted DESC NULLS LAST; CREATE OR REPLACE VIEW monitor.pg_index_bloat_human AS SELECT nspname || '.' || relname AS name, pg_size_pretty(size) AS size, pg_size_pretty((size * ratio)::BIGINT) AS wasted, round(100 * ratio::NUMERIC, 2) as ratio FROM monitor.pg_index_bloat; -- pg session DROP VIEW IF EXISTS monitor.pg_session; CREATE OR REPLACE VIEW monitor.pg_session AS SELECT coalesce(datname, 'all') AS datname, numbackends, active, idle, ixact, max_duration, max_tx_duration, max_conn_duration FROM ( SELECT datname, count(*) AS numbackends, count(*) FILTER ( WHERE state = 'active' ) AS active, count(*) FILTER ( WHERE state = 'idle' ) AS idle, count(*) FILTER ( WHERE state = 'idle in transaction' OR state = 'idle in transaction (aborted)' ) AS ixact, max(extract(epoch from now() - state_change)) FILTER ( WHERE state = 'active' ) AS max_duration, max(extract(epoch from now() - xact_start)) AS max_tx_duration, max(extract(epoch from now() - backend_start)) AS max_conn_duration FROM pg_stat_activity WHERE backend_type = 'client backend' AND pid <> pg_backend_pid() GROUP BY ROLLUP (1) ORDER BY 1 NULLS FIRST ) t; COMMENT ON VIEW monitor.pg_session IS 'postgres session stats'; DROP VIEW IF EXISTS monitor.pg_kill; CREATE OR REPLACE VIEW monitor.pg_kill AS SELECT pid, pg_terminate_backend(pid) AS killed, datname AS dat, usename AS usr, application_name AS app, client_addr AS addr, state, extract(epoch from now() - state_change) AS query_time, extract(epoch from now() - xact_start) AS xact_time, extract(epoch from now() - backend_start) AS conn_time, substring(query, 1, 40) AS query FROM pg_stat_activity WHERE backend_type = 'client backend' AND pid <> pg_backend_pid(); COMMENT ON VIEW monitor.pg_kill IS 'kill all backend session'; -- quick cancel view DROP VIEW IF EXISTS monitor.pg_cancel; CREATE OR REPLACE VIEW monitor.pg_cancel AS SELECT pid, pg_cancel_backend(pid) AS cancel, datname AS dat, usename AS usr, application_name AS app, client_addr AS addr, state, extract(epoch from now() - state_change) AS query_time, extract(epoch from now() - xact_start) AS xact_time, extract(epoch from now() - backend_start) AS conn_time, substring(query, 1, 40) FROM pg_stat_activity WHERE state = 'active' AND backend_type = 'client backend' and pid <> pg_backend_pid(); COMMENT ON VIEW monitor.pg_cancel IS 'cancel backend queries'; -- seq scan DROP VIEW IF EXISTS monitor.pg_seq_scan; CREATE OR REPLACE VIEW monitor.pg_seq_scan AS SELECT schemaname AS nspname, relname, seq_scan, seq_tup_read, seq_tup_read / seq_scan AS seq_tup_avg, idx_scan, n_live_tup + n_dead_tup AS tuples, n_live_tup / (n_live_tup + n_dead_tup) AS dead_ratio FROM pg_stat_user_tables WHERE seq_scan > 0 and (n_live_tup + n_dead_tup) > 0 ORDER BY seq_tup_read DESC LIMIT 50; COMMENT ON VIEW monitor.pg_seq_scan IS 'table that have seq scan'; EOF psql -AXtwq ${database} <<-'EOF' CREATE OR REPLACE FUNCTION monitor.pg_shmem() RETURNS SETOF pg_shmem_allocations AS $$ SELECT * FROM pg_shmem_allocations;$$ LANGUAGE SQL SECURITY DEFINER; EOF done #---------------------------------------------------------------------------- # default database #---------------------------------------------------------------------------- if [ ${PG_DEFAULT_DATABASE} != 'postgres' ]; then log "initdb: create default database: ${PG_DEFAULT_DATABASE}" psql -AXtwq postgres <<- EOF CREATE DATABASE "${PG_DEFAULT_DATABASE}"; EOF if [ ${PG_DEFAULT_SCHEMA} != 'public' ]; then log "initdb: create default schema on ${PG_DEFAULT_DATABASE} : ${PG_DEFAULT_SCHEMA}" psql -AXtwq ${PG_DEFAULT_DATABASE} <<- EOF CREATE SCHEMA IF NOT EXISTS ${PG_DEFAULT_SCHEMA}; ALTER USER "${PG_DBSU}" SET search_path = ${PG_DEFAULT_SCHEMA},public,monitor; EOF fi if [ ${PG_DEFAULT_EXTENSIONS} != '' ]; then log "initdb: create default extensions on ${PG_DEFAULT_DATABASE} : ${PG_DEFAULT_EXTENSIONS}" for ext in ${PG_DEFAULT_EXTENSIONS//,/ } do log "initdb: create extension ${ext};" psql -AXtwq ${PG_DEFAULT_DATABASE} <<- EOF CREATE EXTENSION IF NOT EXISTS ${ext} WITH SCHEMA public; EOF done fi fi #---------------------------------------------------------------------------- # customize commands #---------------------------------------------------------------------------- log "initdb: completed!" pg_exporter-1.4.1/monitor/pgrds-instance.json000066400000000000000000015012041523237665500213770ustar00rootroot00000000000000{ "annotations": { "list": [ { "builtIn": 1, "datasource": { "type": "datasource", "uid": "grafana" }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "target": { "limit": 100, "matchAny": false, "tags": [], "type": "dashboard" }, "type": "dashboard" } ] }, "author": "Ruohang Feng (rh@vonng.com)", "description": "PostgreSQL Monitoring for Remote RDS instances (with limited metrics)", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": null, "license": "AGPLv3 @ https://pigsty.io/docs/about/license", "links": [ { "asDropdown": true, "icon": "external link", "includeVars": true, "keepTime": true, "tags": [ "Pigsty", "PGSQL", "Overview" ], "targetBlank": false, "title": "Overview", "tooltip": "", "type": "dashboards", "url": "" }, { "asDropdown": true, "icon": "external link", "includeVars": true, "keepTime": true, "tags": [ "Pigsty", "PGSQL", "Cluster" ], "targetBlank": false, "title": "Cluster", "tooltip": "", "type": "dashboards", "url": "" }, { "asDropdown": true, "icon": "external link", "includeVars": true, "keepTime": true, "tags": [ "Pigsty", "PGSQL", "Instance" ], "targetBlank": false, "title": "Instance", "tooltip": "", "type": "dashboards", "url": "" }, { "asDropdown": true, "icon": "external link", "includeVars": true, "keepTime": true, "tags": [ "Pigsty", "PGSQL", "Database" ], "targetBlank": false, "title": "Database", "tooltip": "", "type": "dashboards", "url": "" } ], "liveNow": false, "panels": [ { "collapsed": false, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 86, "panels": [], "title": "Overview", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "max": 2.5, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#6986a3", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "Cluster" }, "properties": [ { "id": "displayName", "value": "${cls}" }, { "id": "mappings", "value": [ { "options": { "1": { "index": 0, "text": "Cluster" } }, "type": "value" } ] }, { "id": "links", "value": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&${__url_time_range}" } ] } ] }, { "matcher": { "id": "byName", "options": "Instance" }, "properties": [ { "id": "displayName", "value": "${ins}" }, { "id": "mappings", "value": [ { "options": { "1": { "index": 0, "text": "Instance" } }, "type": "value" } ] }, { "id": "links", "value": [ { "title": "PGCAT Instance : ${ins}", "url": "/d/pgcat-instance?var-dsn=${ins}.${datname}" } ] } ] }, { "matcher": { "id": "byName", "options": "IP" }, "properties": [ { "id": "mappings", "value": [ { "options": { "1": { "index": 0, "text": "Node IP" } }, "type": "value" } ] }, { "id": "displayName", "value": "${ip}" }, { "id": "links", "value": [ { "title": "Node Instance : ${ip}", "url": "/d/node-instance?var-id=${ip}&${__url_time_range}" } ] } ] }, { "matcher": { "id": "byName", "options": "Name" }, "properties": [ { "id": "displayName", "value": "${node}" }, { "id": "mappings", "value": [ { "options": { "1": { "index": 0, "text": "Hostname" } }, "type": "value" } ] }, { "id": "links", "value": [ { "title": "Node Instance : ${node}", "url": "/d/node-instance?var-id=${node}&${__url_time_range}" } ] } ] } ] }, "gridPos": { "h": 4, "w": 6, "x": 0, "y": 1 }, "id": 189, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 20, "valueSize": 16 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "1", "hide": false, "instant": true, "interval": "", "legendFormat": "Instance", "queryType": "measurements", "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "1", "hide": false, "instant": true, "interval": "", "legendFormat": "Cluster", "queryType": "measurements", "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "All instances among pgsql cluster ${cls}\n\nInstance: Goto PGSQL Instance\n\nIP: Goto PGSQL Node\n\nStatus: Goto PGSQL Service\n\nLoad: max(cpu,postgres,pgbouncer)\n\nSpace: Disk space usage max(all device)\n\nProxy: session number, Goto Haproxy Admin Page", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "custom": { "align": "center", "cellOptions": { "type": "auto" }, "inspect": false }, "mappings": [], "max": 1.2, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#e3e3e3e0", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "Instance" }, "properties": [ { "id": "links", "value": [ { "title": "PGRDS Instance ${__data.fields.Instance}", "url": "/d/pgrds-instance?var-ins=${__data.fields.Instance}&${__url_time_range}" } ] }, { "id": "custom.width" } ] }, { "matcher": { "id": "byName", "options": "Host" }, "properties": [ { "id": "links", "value": [] } ] }, { "matcher": { "id": "byName", "options": "Role" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "mappings", "value": [ { "options": { "0": { "color": "#3e668f", "index": 0, "text": "primary" }, "1": { "color": "#346f36cc", "index": 1, "text": "replica" } }, "type": "value" } ] }, { "id": "custom.cellOptions", "value": { "mode": "gradient", "type": "color-background" } }, { "id": "links", "value": [ { "title": "PGSQL Service for ${cls}-${__data.fields.Role}", "url": "/d/pgsql-service?var-svc=${cls}-${__data.fields.Role}&${__url_time_range}" } ] } ] }, { "matcher": { "id": "byName", "options": "Load" }, "properties": [ { "id": "custom.cellOptions", "value": { "type": "auto" } }, { "id": "color", "value": { "mode": "thresholds" } }, { "id": "unit", "value": "short" }, { "id": "custom.width", "value": 120 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 0.1 }, { "color": "#fcdb72", "value": 0.3 }, { "color": "#f5a673", "value": 0.5 }, { "color": "red", "value": 0.7 }, { "color": "#b783af", "value": 0.9 }, { "color": "text", "value": 1 } ] } }, { "id": "decimals", "value": 2 }, { "id": "mappings", "value": [ { "options": { "-1": { "color": "transparent", "index": 0, "text": "N/A" } }, "type": "value" } ] } ] }, { "matcher": { "id": "byName", "options": "Cluster" }, "properties": [ { "id": "links", "value": [ { "title": "PGSQL Cluster for ${__data.fields.Cluster}", "url": "/d/pgsql-cluster?var-cls=${__data.fields.Cluster}&${__url_time_range}" } ] } ] }, { "matcher": { "id": "byName", "options": "TPS" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#3e668f", "value": 32 }, { "color": "#f5a673", "value": 32000 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } } ] }, { "matcher": { "id": "byName", "options": "DB Conn" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 4 }, { "color": "#EAB839", "value": 20 }, { "color": "#EF843C", "value": 40 }, { "color": "#E24D42", "value": 80 }, { "color": "#b783af", "value": 100 }, { "color": "text", "value": 400 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } } ] }, { "matcher": { "id": "byName", "options": "RT" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "unit", "value": "s" }, { "id": "mappings", "value": [ { "options": { "match": "nan", "result": { "index": 1, "text": "-" } }, "type": "special" } ] }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "#fcdb72", "value": 1 }, { "color": "#f5a673", "value": 2 }, { "color": "red", "value": 4 }, { "color": "#b783af", "value": 8 }, { "color": "text", "value": 16 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } } ] }, { "matcher": { "id": "byName", "options": "Session" }, "properties": [ { "id": "custom.width", "value": 72 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 1 }, { "color": "#fcdb72", "value": 1000 }, { "color": "#f5a673", "value": 2000 }, { "color": "red", "value": 3000 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } } ] }, { "matcher": { "id": "byName", "options": "Queue" }, "properties": [ { "id": "custom.width", "value": 60 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "red", "value": 1 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } } ] }, { "matcher": { "id": "byName", "options": "HAProxy" }, "properties": [ { "id": "links", "value": [ { "title": "HAProxy Admin Page : ${__data.fields.Instance}", "url": "http://h.pigsty/${__data.fields.Instance}" } ] } ] }, { "matcher": { "id": "byName", "options": "Up" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "mappings", "value": [ { "options": { "0": { "color": "#cc4637d9", "index": 0, "text": "Dead" }, "1": { "color": "#346f36cc", "index": 1, "text": "Alive" } }, "type": "value" } ] }, { "id": "custom.cellOptions", "value": { "type": "color-background" } } ] }, { "matcher": { "id": "byName", "options": "UPTime" }, "properties": [ { "id": "unit", "value": "s" }, { "id": "decimals", "value": 1 }, { "id": "custom.width", "value": 100 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "red", "value": null }, { "color": "#f5a673", "value": 300 }, { "color": "#eab839", "value": 3600 }, { "color": "#346f36cc", "value": 86400 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } } ] } ] }, "gridPos": { "h": 6, "w": 18, "x": 6, "y": 1 }, "id": 191, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&${__url_time_range}" } ], "options": { "cellHeight": "sm", "footer": { "countRows": false, "fields": "", "reducer": [ "sum" ], "show": false }, "showHeader": true, "sortBy": [ { "desc": false, "displayName": "Instance" } ] }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "min by (ins,ip) (pg_up{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pg:ins:active_time_rate1m{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "sum by (ins) (pg:ins:xact_total_rate1m{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "min by (ins,ip) (pg_in_recovery{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "D" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "max by (ins) (pg:ins:num_backends{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "E" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "max by (ins) (pgbouncer:ins:xact_rt_1m{cls=\"$cls\"})", "format": "table", "hide": true, "instant": true, "interval": "", "legendFormat": "", "refId": "F" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pg_uptime{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "G" } ], "transformations": [ { "id": "seriesToColumns", "options": { "byField": "ins" } }, { "id": "organize", "options": { "excludeByName": { "Time": true, "Time 1": true, "Time 10": true, "Time 2": true, "Time 3": true, "Time 4": true, "Time 6": true, "Time 8": true, "Time 9": true, "Value #A": false, "Value #B": false, "Value #H": false, "__name__": true, "__name__ 1": true, "__name__ 2": true, "__name__ 3": true, "__name__ 5": true, "__name__ 7": true, "cls": true, "cls 1": true, "cls 2": true, "cls 3": true, "cls 4": true, "cls 5": true, "cls 6": true, "cls 7": true, "instance": false, "instance 1": true, "instance 2": false, "instance 3": true, "instance 4": true, "instance 5": true, "instance 6": true, "ip 2": true, "ip 3": true, "ip 4": true, "ip 5": true, "ip 6": true, "ip 7": true, "job": true, "job 1": true, "job 2": true, "job 3": true, "job 4": true, "job 5": true, "job 6": true, "job 7": true }, "indexByName": { "Time 1": 7, "Time 2": 8, "Time 3": 9, "Time 4": 10, "Time 5": 11, "Value #A": 2, "Value #B": 4, "Value #C": 5, "Value #D": 3, "Value #E": 6, "ins": 0, "ip 1": 1, "ip 2": 12 }, "renameByName": { "Time 4": "", "Value #A": "Up", "Value #B": "Load", "Value #C": "TPS", "Value #D": "Role", "Value #E": "Session", "Value #F": "RT", "Value #G": "UPTime", "Value #H": "LB", "Value #I": "QPS", "Value #J": "LB Clients", "Value #K": "Lag", "cls 1": "", "cls 2": "", "ins": "Instance", "instance": "HAProxy", "instance 2": "", "ip": "IP", "ip 1": "Host" } } } ], "type": "table" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "max": 2.5, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#6986a3", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 2, "w": 6, "x": 0, "y": 5 }, "id": 190, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "/.*/", "values": false }, "showPercentChange": false, "text": { "valueSize": 12 }, "textMode": "value", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (address) (label_join(pg_meta_info{ins=\"$ins\"}, \"address\", \":\", \"ip\", \"listen_port\"))", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "__auto", "queryType": "measurements", "refId": "A" } ], "transformations": [ { "id": "organize", "options": { "excludeByName": { "Time": true, "Value": true, "__name__": true, "cls": true, "cluster_id": true, "ins": true, "instance": true, "ip": false, "job": true, "ver_num": true, "version": true, "wal_level": true }, "indexByName": { "Time": 0, "Value": 9, "__name__": 1, "cls": 2, "cluster_id": 3, "ins": 4, "instance": 12, "ip": 10, "job": 5, "listen_port": 11, "ver_num": 8, "version": 6, "wal_level": 7 }, "renameByName": { "cls": "Cluster", "cluster_id": "Cluster ID", "ins": "Leader", "ip": "Host", "listen_port": "Port", "version": "Version", "wal_level": "WAL Level" } } }, { "id": "filterFieldsByName", "options": {} } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "max": 2.5, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#6986a3", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "Leader" }, "properties": [ { "id": "links", "value": [ { "title": "PGRDS Leader : ${primary}", "url": "/d/pgrds-instance?var-ins=${primary}&${__url_time_range}" } ] } ] }, { "matcher": { "id": "byName", "options": "Kernel" }, "properties": [ { "id": "mappings", "value": [ { "options": { "pattern": ".*wiltondb.*", "result": { "color": "#d82024e3", "index": 0, "text": "Babelfish" } }, "type": "regex" }, { "options": { "pattern": ".*PolarDB.*", "result": { "color": "#f25009", "index": 1, "text": "PolarDB" } }, "type": "regex" }, { "options": { "pattern": ".*IvorySQL.*", "result": { "color": "#ee9832", "index": 2, "text": "IvorySQL" } }, "type": "regex" }, { "options": { "pattern": "PostgreSQL.*", "result": { "color": "#3e668f", "index": 3, "text": "PostgreSQL" } }, "type": "regex" } ] } ] } ] }, "gridPos": { "h": 6, "w": 6, "x": 0, "y": 7 }, "id": 209, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "/.*/", "values": false }, "showPercentChange": false, "text": { "titleSize": 14, "valueSize": 16 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_meta_info{ins=\"$ins\"}", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "__auto", "queryType": "measurements", "refId": "A" } ], "transformations": [ { "id": "organize", "options": { "excludeByName": { "Time": true, "Value": true, "__name__": true, "cls": true, "cluster_id": false, "cluster_name": true, "conf_path": false, "data_dir": false, "extensions": true, "hba_path": false, "ins": true, "instance": true, "ip": true, "job": true, "listen_port": true, "primary_conninfo": true, "ver_num": false, "ver_str": false, "version": true }, "includeByName": {}, "indexByName": { "Time": 3, "Value": 10, "__name__": 4, "cls": 5, "cluster_id": 9, "cluster_name": 14, "conf_path": 15, "data_dir": 17, "extensions": 18, "hba_path": 16, "ins": 6, "instance": 13, "ip": 11, "job": 7, "listen_port": 12, "ver_num": 1, "ver_str": 0, "version": 2, "wal_level": 8 }, "renameByName": { "cls": "Cluster", "cluster_id": "Cluster ID", "conf_path": "Config", "data_dir": "Data", "hba_path": "HBA", "ins": "Leader", "ip": "Host", "listen_port": "Port", "ver_num": "Version", "ver_str": "Kernel", "version": "Version", "wal_level": "WAL Level" } } } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGSQL Replication for ${cls}", "url": "/d/pgsql-replication?var-cls=${cls}&${__url_time_range}" } ], "mappings": [], "max": 2.5, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "mappings", "value": [ { "options": { "0": { "color": "#f5a673", "index": 0, "text": "Primary" }, "1": { "color": "#346f36cc", "index": 1, "text": "Replica" } }, "type": "value" } ] }, { "id": "displayName", "value": "Role" } ] }, { "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "mappings", "value": [ { "options": { "0": { "color": "#3e668f", "index": 0, "text": "No" }, "1": { "color": "#346f36cc", "index": 1, "text": "Yes" } }, "type": "value" } ] } ] }, { "matcher": { "id": "byFrameRefID", "options": "C" }, "properties": [ { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 1 } ] } } ] }, { "matcher": { "id": "byFrameRefID", "options": "D" }, "properties": [ { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] } }, { "id": "mappings", "value": [ { "options": { "0": { "color": "#f5a673", "index": 0, "text": "Leading" }, "1": { "color": "#3e668f", "index": 1, "text": "Follow" } }, "type": "value" } ] }, { "id": "links", "value": [ { "title": "Node Instance : ${__field.labels.sender_host}", "url": "/d/node-instance?var-id=${__field.labels.sender_host}&${__url_time_range}" } ] } ] }, { "matcher": { "id": "byFrameRefID", "options": "E" }, "properties": [ { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 1 } ] } } ] }, { "matcher": { "id": "byFrameRefID", "options": "F" }, "properties": [ { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 1 } ] } } ] } ] }, "gridPos": { "h": 6, "w": 3, "x": 6, "y": 7 }, "id": 194, "links": [ { "title": "PGSQL Replication : ${cls}", "url": "/d/pgsql-replication?var-cls=${cls}&${__url_time_range}" } ], "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 10, "valueSize": 10 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "pg_in_recovery{ins=\"$ins\"}", "instant": true, "interval": "", "legendFormat": "recovery", "queryType": "measurements", "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "(pg_recv_init_lsn{ins=\"$ins\"} > bool 0) or on() vector(0)", "hide": false, "instant": true, "interval": "", "legendFormat": "{{ sender_host }} ", "queryType": "measurements", "refId": "D" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "count(pg_repl_lsn{ins=\"$ins\"}) or on() vector(0)", "hide": false, "instant": true, "interval": "", "legendFormat": "Downstream", "queryType": "measurements", "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_sync_standby_enabled{ins=\"$ins\"}", "hide": false, "instant": true, "interval": "", "legendFormat": "Sync Commit", "queryType": "measurements", "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "count by (ins)(pg_pubrel_count{ins=\"$ins\"}) > bool 0 or on() vector(0)", "hide": false, "instant": true, "interval": "", "legendFormat": "Publication", "queryType": "measurements", "refId": "E" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "count by (ins)(pg_sub_id{ins=\"$ins\"}) or on() vector(0)", "hide": false, "instant": true, "interval": "", "legendFormat": "Subscription", "queryType": "measurements", "refId": "F" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [ { "options": { "match": "null+nan", "result": { "color": "transparent", "index": 1 } }, "type": "special" } ], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "WAL Paused" }, "properties": [ { "id": "mappings", "value": [ { "options": { "0": { "color": "#346f36cc", "index": 0, "text": "Running" }, "1": { "color": "#f5a673", "index": 1, "text": "Paused" } }, "type": "value" }, { "options": { "match": "null+nan", "result": { "color": "transparent", "index": 2 } }, "type": "special" } ] } ] }, { "matcher": { "id": "byName", "options": "WAL" }, "properties": [ { "id": "mappings", "value": [ { "options": { "0": { "color": "#346f36cc", "index": 0, "text": "Proceed" }, "1": { "color": "#f5a673", "index": 1, "text": "Paused" } }, "type": "value" }, { "options": { "match": "null+nan", "result": { "color": "transparent", "index": 2 } }, "type": "special" } ] } ] }, { "matcher": { "id": "byName", "options": "PG Uptime" }, "properties": [ { "id": "unit", "value": "s" }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#cc4637d9", "value": null }, { "color": "#f5a673", "value": 300 }, { "color": "#fcdb72", "value": 3600 }, { "color": "#346f36cc", "value": 86400 } ] } } ] }, { "matcher": { "id": "byName", "options": "Aliveness" }, "properties": [ { "id": "mappings", "value": [ { "options": { "0": { "color": "#cc4637d9", "index": 0, "text": "Dead" }, "1": { "color": "#346f36cc", "index": 1, "text": "Alive" } }, "type": "value" } ] } ] }, { "matcher": { "id": "byName", "options": "Last Reload" }, "properties": [ { "id": "unit", "value": "s" } ] } ] }, "gridPos": { "h": 6, "w": 3, "x": 9, "y": 7 }, "id": 193, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 10, "valueSize": 10 }, "textMode": "auto", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_up{ins=\"$ins\"}", "format": "table", "instant": true, "legendFormat": "__auto", "range": false, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_uptime{ins=\"$ins\"}", "format": "table", "hide": false, "instant": true, "legendFormat": "__auto", "range": false, "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_version{ins=\"$ins\"}", "format": "table", "hide": false, "instant": true, "legendFormat": "__auto", "range": false, "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_conf_reload_time{ins=\"$ins\"}", "format": "table", "hide": false, "instant": true, "legendFormat": "__auto", "range": false, "refId": "D" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:timeline{ins=\"$ins\"}", "format": "table", "hide": false, "instant": true, "legendFormat": "__auto", "range": false, "refId": "E" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_is_wal_replay_paused{ins=\"$ins\"}", "format": "table", "hide": false, "instant": true, "legendFormat": "__auto", "range": false, "refId": "F" } ], "transformations": [ { "id": "joinByField", "options": { "byField": "ins", "mode": "outer" } }, { "id": "organize", "options": { "excludeByName": { "Time": true, "Time 1": true, "Time 10": true, "Time 11": true, "Time 12": true, "Time 2": true, "Time 3": true, "Time 4": true, "Time 5": true, "Time 6": true, "Time 7": true, "Time 8": true, "Time 9": true, "Value #C": false, "Value #E": false, "Value #H": true, "Value #I": true, "Value #K": true, "__name__ 1": false, "cls": true, "ins": true, "scope": false }, "indexByName": { "Time 1": 6, "Time 2": 7, "Time 3": 8, "Time 4": 9, "Time 5": 10, "Time 6": 11, "Value #A": 0, "Value #B": 2, "Value #C": 5, "Value #D": 3, "Value #E": 4, "Value #F": 1, "__name__ 1": 12, "__name__ 2": 18, "__name__ 3": 23, "__name__ 4": 28, "__name__ 5": 33, "__name__ 6": 38, "cls 1": 14, "cls 2": 19, "cls 3": 24, "cls 4": 29, "cls 5": 34, "cls 6": 39, "ins": 13, "instance 1": 15, "instance 2": 20, "instance 3": 25, "instance 4": 30, "instance 5": 35, "instance 6": 40, "ip 1": 16, "ip 2": 21, "ip 3": 26, "ip 4": 31, "ip 5": 36, "ip 6": 41, "job 1": 17, "job 2": 22, "job 3": 27, "job 4": 32, "job 5": 37, "job 6": 42 }, "renameByName": { "Value #A": "Aliveness", "Value #B": "PG Uptime", "Value #C": "Version", "Value #D": "Last Reload", "Value #E": "Timeline", "Value #F": "WAL Paused", "Value #G": "", "Value #H": "", "Value #I": "", "Value #J": "", "Value #K": "", "Value #L": "Up", "cls": "Cluster", "ins": "Instance", "scope": "Shard" } } } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Database of this cluster ${cls}\n\nLoad: active_time / backends (PG14 only)\n\nQuery RT : max query rt of this database\n\nClient: Pgbouncer Active Clients\n\nServer: Pgbouncer Active Servers\n\nBackend: Postgres num backends\n\nT = is Template?\n\nD = Disabled in Pgbouncer ?\n\nP = Paused in Pgbouncer ?", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "custom": { "align": "center", "cellOptions": { "mode": "gradient", "type": "color-background" }, "inspect": false }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#e3e3e3e0", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "Database" }, "properties": [ { "id": "links", "value": [ { "title": "PGSQL Database : ${ins}.${__data.fields.Database}", "url": "/d/pgsql-database?var-ins=${ins}&var-datname=${__data.fields.Database}&${__url_time_range}" } ] }, { "id": "mappings", "value": [ { "options": { "postgres": { "color": "#808080", "index": 2 }, "template0": { "color": "#c0c0c0e0", "index": 0 }, "template1": { "color": "#a8a8a8e0", "index": 1 } }, "type": "value" } ] }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } }, { "id": "filterable" } ] }, { "matcher": { "id": "byName", "options": "Size" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "unit", "value": "decbytes" }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 1000000000 }, { "color": "#fcdb72", "value": 1000000000000 }, { "color": "#f5a673", "value": 10000000000000 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "color", "value": { "mode": "thresholds" } }, { "id": "decimals", "value": 0 } ] }, { "matcher": { "id": "byName", "options": "Age" }, "properties": [ { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 60000000 }, { "color": "#EAB839", "value": 200000000 }, { "color": "#f5a673", "value": 600000000 }, { "color": "red", "value": 1000000000 }, { "color": "#b783af", "value": 2000000000 }, { "color": "text", "value": 2137483647 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "unit", "value": "percentunit" }, { "id": "custom.width", "value": 60 }, { "id": "color", "value": { "mode": "thresholds" } }, { "id": "decimals", "value": 1 } ] }, { "matcher": { "id": "byName", "options": "TPS" }, "properties": [ { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "#f5a673", "value": 30000 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "custom.width", "value": 70 }, { "id": "color", "value": { "mode": "thresholds" } }, { "id": "decimals", "value": 1 } ] }, { "matcher": { "id": "byName", "options": "T" }, "properties": [ { "id": "custom.width", "value": 40 }, { "id": "mappings", "value": [ { "options": { "0": { "color": "#346f36cc", "index": 1, "text": "F" }, "1": { "color": "#f5a673", "index": 2, "text": "T" } }, "type": "value" }, { "options": { "match": "null+nan", "result": { "color": "#e3e3e3e0", "index": 0, "text": "N" } }, "type": "special" } ] }, { "id": "custom.cellOptions", "value": { "mode": "gradient", "type": "color-background" } } ] }, { "matcher": { "id": "byName", "options": "Backend" }, "properties": [ { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 4 }, { "color": "#f5a673", "value": 1000 } ] } }, { "id": "color", "value": { "mode": "thresholds" } }, { "id": "custom.width", "value": 75 }, { "id": "custom.cellOptions", "value": { "type": "color-text" } } ] }, { "matcher": { "id": "byName", "options": "ConnLimit" }, "properties": [ { "id": "custom.width", "value": 85 }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "color" }, { "id": "mappings", "value": [ { "options": { "-1": { "color": "#3e668f", "index": 1, "text": "NO LIMIT" } }, "type": "value" }, { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "N/A" } }, "type": "special" }, { "options": { "from": 0, "result": { "color": "#f5a673", "index": 2 }, "to": 99999999 }, "type": "range" } ] } ] }, { "matcher": { "id": "byRegexp", "options": "Conn%" }, "properties": [ { "id": "unit", "value": "percentunit" }, { "id": "custom.width", "value": 80 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 0.1 }, { "color": "#fcdb72", "value": 0.3 }, { "color": "#f5a673", "value": 0.5 }, { "color": "red", "value": 0.7 }, { "color": "#b783af", "value": 0.9 }, { "color": "text", "value": 0.99 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "decimals", "value": 1 } ] }, { "matcher": { "id": "byName", "options": "Client" }, "properties": [ { "id": "custom.width", "value": 60 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "#fcdb72", "value": 1000 } ] } }, { "id": "custom.cellOptions", "value": { "type": "color-text" } } ] }, { "matcher": { "id": "byName", "options": "Server" }, "properties": [ { "id": "custom.width", "value": 60 }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 4 }, { "color": "#fcdb72", "value": 1000 } ] } } ] }, { "matcher": { "id": "byName", "options": "RT" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "#fcdb72", "value": 1 }, { "color": "#f5a673", "value": 2 }, { "color": "red", "value": 4 }, { "color": "#b783af", "value": 8 }, { "color": "text", "value": 16 } ] } }, { "id": "mappings", "value": [ { "options": { "match": "null+nan", "result": { "index": 0, "text": "-" } }, "type": "special" } ] }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "unit", "value": "s" } ] }, { "matcher": { "id": "byName", "options": "Load" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "unit", "value": "short" }, { "id": "decimals", "value": 1 }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 0.1 } ] } }, { "id": "mappings", "value": [ { "options": { "match": "null+nan", "result": { "index": 0, "text": "-" } }, "type": "special" } ] } ] }, { "matcher": { "id": "byName", "options": "PoolSize" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "custom.cellOptions", "value": { "type": "auto" } } ] }, { "matcher": { "id": "byName", "options": "OID" }, "properties": [ { "id": "custom.width", "value": 80 }, { "id": "links", "value": [ { "title": "PGCAT Database : ${ins}.${__data.fields.Database}", "url": "/d/pgcat-database?var-dsn=${ins}.${__data.fields.Database}" } ] }, { "id": "mappings", "value": [ { "options": { "from": 0, "result": { "color": "#bfbfbf4d", "index": 0 }, "to": 10 }, "type": "range" } ] }, { "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "thresholds", "value": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null } ] } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 7 }, "id": 192, "options": { "cellHeight": "sm", "footer": { "countRows": false, "fields": "", "reducer": [ "sum" ], "show": false }, "showHeader": true, "sortBy": [ { "desc": true, "displayName": "OID" } ] }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "sum by (datname) (pg_size_bytes{cls=\"$cls\", datname!~\"wal|log\"})", "format": "table", "instant": true, "interval": "", "legendFormat": "Age", "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "max by (datname) (pg:db:age{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "Commit", "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "max by (datname) (pg_db_is_template{cls=\"$cls\", ins=\"$primary\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "isTemplate", "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg_activity_count{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "Backend", "refId": "D" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "sum by (datname) (pg:db:xact_commit_rate1m{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "TPS", "refId": "E" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "max by (datname) (pg_db_conn_limit{ins=\"$primary\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "Conn Limit", "refId": "F" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "max by (datname) (pg:db:conn_usage{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "PG Conn Usage", "refId": "H" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg:db:active_time_rate1m{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "Load", "refId": "L" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (datname) (pg_db_datid{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "OID", "refId": "O" } ], "transformations": [ { "id": "seriesToColumns", "options": { "byField": "datname" } }, { "id": "organize", "options": { "excludeByName": { "Time 1": true, "Time 10": true, "Time 11": true, "Time 12": true, "Time 13": true, "Time 14": true, "Time 15": true, "Time 2": true, "Time 3": true, "Time 4": true, "Time 5": true, "Time 6": true, "Time 7": true, "Time 8": true, "Time 9": true, "Value #G": false, "Value #H": false, "host": true }, "indexByName": { "Time 1": 10, "Time 2": 11, "Time 3": 12, "Time 4": 13, "Time 5": 14, "Time 6": 15, "Time 7": 16, "Time 8": 17, "Time 9": 18, "Value #A": 3, "Value #B": 4, "Value #C": 9, "Value #D": 5, "Value #E": 6, "Value #F": 8, "Value #H": 7, "Value #L": 2, "Value #O": 1, "datname": 0 }, "renameByName": { "Time 1": "", "Time 7": "", "Value #A": "Size", "Value #B": "Age", "Value #C": "T", "Value #D": "Backend", "Value #E": "TPS", "Value #F": "ConnLimit", "Value #G": "RT", "Value #H": "Conn%", "Value #I": "D", "Value #J": "P", "Value #K": "PoolSize", "Value #L": "Load", "Value #M": "Client", "Value #N": "Server", "Value #O": "OID", "datname": "Database", "host": "Target", "real_datname": "Datname" } } } ], "type": "table" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGSQL Instance : TPS by Instance", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=94&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 32 }, { "color": "#f5a673", "value": 32000 } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 0, "y": 13 }, "id": 46, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 18 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:xact_commit_rate1m{ins=\"$ins\"}", "interval": "", "legendFormat": "Commit", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGSQL Instance : Rollback", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=169&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "#fcdb72", "value": 1 }, { "color": "#f5a673", "value": 4 }, { "color": "red", "value": 8 } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 2, "y": 13 }, "id": 57, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 18 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:xact_rollback_rate1m{ins=\"$ins\"}", "interval": "", "legendFormat": "Rollback", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGSQL Instance : RT", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=109&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "#fcdb72", "value": 1 }, { "color": "#f5a673", "value": 2 }, { "color": "red", "value": 4 }, { "color": "#b783af", "value": 8 }, { "color": "text", "value": 16 } ] }, "unit": "s" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 4, "y": 13 }, "id": 59, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 18 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:active_time_rate1m{ins=\"$ins\"} / pg:ins:xact_total_rate1m{ins=\"$ins\"}", "interval": "", "legendFormat": "RT", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGRDS Instance : Conn Usage", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=90&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 0.1 }, { "color": "#fcdb72", "value": 0.3 }, { "color": "#f5a673", "value": 0.5 }, { "color": "red", "value": 0.7 }, { "color": "#b783af", "value": 0.9 }, { "color": "text", "value": 1 } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 6, "y": 13 }, "id": 106, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 18 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max(pg:db:conn_usage{cls=\"$cls\"})", "interval": "", "legendFormat": "Conn%", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGRDS Instance : Backend by Instance", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=96&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 4 }, { "color": "#f5a673", "value": 2000 } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 8, "y": 13 }, "id": 64, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 18 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "exemplar": false, "expr": "sum by (cls) (pg_db_numbackends{cls=\"$cls\"})", "interval": "", "legendFormat": "Backend", "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGRDS Instance : Session by State", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=149&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 4 }, { "color": "#f5a673", "value": 2000 } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 10, "y": 13 }, "id": 107, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 18 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:active_backends{ins=\"$ins\"}", "interval": "", "legendFormat": "Active Conn", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGRDS Instance : Idle in Transaction Conn", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=159&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "#f5a673", "value": 1 }, { "color": "red", "value": 8 } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 12, "y": 13 }, "id": 203, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 18 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max(pg:ins:ixact_backends{ins=\"$ins\"})", "interval": "", "legendFormat": "iXact Conn", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "decimals": 2, "links": [ { "title": "PGSQL Instance : Row Fetched", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=127&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 32 }, { "color": "#f5a673", "value": 32000 } ] }, "unit": "rowsps" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 14, "y": 13 }, "id": 204, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 12 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:tup_fetched_rate1m{ins=\"$ins\"}", "interval": "", "legendFormat": "Row Fetch", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "decimals": 2, "links": [ { "title": "PGRDS Instance : Row Changed", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=128&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 32 }, { "color": "#f5a673", "value": 32000 } ] }, "unit": "rowsps" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 16, "y": 13 }, "id": 205, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 12 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:tup_modified_rate1m{ins=\"$ins\"}", "interval": "", "legendFormat": "Row Change", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGSQL Instance : Blocks Read Bandwidth", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=182&${__url_time_range}" } ], "mappings": [], "min": 0, "noValue": "\u2205", "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 10000000 }, { "color": "#fcdb72", "value": 100000000 }, { "color": "#f5a673", "value": 500000000 }, { "color": "red", "value": 1000000000 }, { "color": "#b783af", "value": 2000000000 }, { "color": "text", "value": 4000000000 } ] }, "unit": "Bps" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 18, "y": 13 }, "id": 206, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 14 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum(pg:db:blks_read_1m{ins=\"$ins\"}) * 4096", "interval": "", "legendFormat": "Blks Read", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "decimals": 2, "links": [ { "title": "PGRDS Instance : Age of Databases", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=208&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "max": 1, "min": 0, "thresholds": { "mode": "percentage", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "#fcdb72", "value": 2 }, { "color": "#f5a673", "value": 10 }, { "color": "red", "value": 30 }, { "color": "#b783af", "value": 90 }, { "color": "text", "value": 100 } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 20, "y": 13 }, "id": 63, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 18 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:age{ins=\"$ins\"} / 2147483647", "interval": "", "legendFormat": "Age", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "title": "PGRDS Instance : Database Size", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=176&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 1000000000 }, { "color": "#fcdb72", "value": 1000000000000 }, { "color": "#f5a673", "value": 10000000000000 } ] }, "unit": "decbytes" }, "overrides": [] }, "gridPos": { "h": 3, "w": 2, "x": 22, "y": 13 }, "id": 202, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 12, "valueSize": 18 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum(pg_size_bytes{ins=\"$ins\"})", "interval": "", "legendFormat": "Size", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Load, 1 = 100% one cpu core usage.\nCalculated by session active time (PG14+)", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "decimals": 2, "links": [ { "title": "PGRDS Instance : Load", "url": "/d/pgrds-instance?var-ins=${ins}&viewPanel=147&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 0.1 } ] }, "unit": "short" }, "overrides": [] }, "gridPos": { "h": 5, "w": 3, "x": 0, "y": 16 }, "id": 199, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": {}, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:cls:active_time_rate1m{cls=\"$cls\"}", "interval": "", "legendFormat": "Load", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Load for Instances / Databases in last 5 minutes\nload 1 = 100% usage of 1 core cpu time", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 20, "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineStyle": { "fill": "solid" }, "lineWidth": 1, "pointSize": 3, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGRDS Instance : ${__field.labels.ins}", "url": "/d/pgrds-instance?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "short" }, "overrides": [] }, "gridPos": { "h": 5, "w": 9, "x": 3, "y": 16 }, "id": 196, "options": { "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Last", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:db:active_time_rate1m{ins=\"$ins\", datname!~'template\\\\d'}", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "links": [ { "targetBlank": true, "title": "PGSQL Alert for Cluster ${cls}", "url": "http://a.pigsty/#/alerts?filter=%7Bcls%3D%22${cls}%22%7D" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "#fcdb72", "value": 1 }, { "color": "#f5a673", "value": 2 }, { "color": "red", "value": 3 }, { "color": "#b783af", "value": 4 } ] } }, "overrides": [] }, "gridPos": { "h": 5, "w": 3, "x": 12, "y": 16 }, "id": 198, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": {}, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.1.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "count(ALERTS{ins=\"$ins\", alertstate=\"firing\"}) or on() vector(0)", "interval": "", "legendFormat": "Alert", "range": true, "refId": "A" } ], "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Alerting event graph. Each stripe represent an alerting Event. Gray transparent stipe are pending alerts.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "hidden", "axisSoftMax": 1.2, "axisSoftMin": 0, "barAlignment": 0, "drawStyle": "line", "fillOpacity": 98, "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "links": [ { "targetBlank": true, "title": "AlertInfo ${__field.labels.alertname} on Instance ${ins}", "url": "http://a.pigsty/#/alerts?filter=%7Bins%3D%22${ins}%22%2C%20alertname%3D%22${__field.labels.alertname}%22%7D" }, { "targetBlank": true, "title": "Silence ${__field.labels.alertname} on Cluster ${ins}", "url": "http://a.pigsty/#/silences/new?filter=%7Bins%3D%22${ins}%22%2C%20alertname%3D%22${__field.labels.alertname}%22%7D" }, { "title": "PGSQL Cluster for ${__field.labels.cls} : Alerts", "url": "/d/pgsql-cluster?var-cls=${__field.labels.cls}&${__url_time_range}" }, { "title": "PGSQL Node for ${__field.labels.ins}", "url": "/d/node-instance?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "none" }, "overrides": [ { "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "custom.fillOpacity", "value": 66 }, { "id": "mappings", "value": [ { "options": { "1": { "index": 0, "text": "Firing\ud83d\udd25" } }, "type": "value" } ] } ] }, { "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "custom.fillOpacity", "value": 25 }, { "id": "color", "value": { "fixedColor": "rgba(128, 128, 128, 0.5)", "mode": "fixed" } }, { "id": "mappings", "value": [ { "options": { "1": { "color": "gray", "index": 0, "text": "Pend\u23f0" } }, "type": "value" } ] } ] }, { "matcher": { "id": "byType", "options": "time" }, "properties": [ { "id": "custom.axisPlacement", "value": "auto" } ] }, { "matcher": { "id": "byName", "options": "Alert" }, "properties": [ { "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 5, "w": 9, "x": 15, "y": 16 }, "id": 197, "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "ALERTS{ins=\"$ins\", alertstate=\"firing\"}", "interval": "", "legendFormat": "[{{ severity }}\ud83d\udd25] {{alertname}}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "ALERTS{ins=\"$ins\", alertstate=\"pending\"} ", "hide": false, "interval": "", "legendFormat": "[{{ severity }}\u23f0] {{alertname}}", "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "exemplar": false, "expr": "0", "hide": false, "interval": "", "legendFormat": "Alert", "refId": "C" } ], "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 21 }, "id": 133, "panels": [], "title": "Activity", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Rate in last 5min of:\n\n\npg_stat_database. xact_commit: Number of transactions in this database that have been committed\n\npg_stat_database. xact_rollback: Number of transactions in this database that have been rolled back\n\non this postgres instance.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 20, "gradientMode": "hue", "hideFrom": { "graph": false, "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "short" }, "overrides": [ { "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }, { "id": "custom.axisPlacement", "value": "auto" } ] }, { "matcher": { "id": "byName", "options": "Commits" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Rollbacks" }, "properties": [ { "id": "color", "value": { "fixedColor": "#cc4637d9", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 22 }, "id": 169, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=169&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "8.0.0-beta2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (ins) (pg:ins:xact_commit_rate5m{ins=\"$ins\"})", "hide": false, "interval": "", "intervalFactor": 1, "legendFormat": "Commits", "range": true, "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (ins) (pg:ins:xact_rollback_rate5m{ins=\"$ins\"})", "hide": false, "interval": "", "intervalFactor": 1, "legendFormat": "Rollbacks", "range": true, "refId": "A" } ], "title": "Transaction Commits / Rollbacks (rate5m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "pg_stat_database: active_time / (xact_commit + xact_rollback)\n\n\nTime spent executing SQL statements in this instance / Number of transactions in this instance in last 1 minute\n\n\nOnly available on PG14+\n", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "opacity", "hideFrom": { "graph": false, "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "s" }, "overrides": [ { "matcher": { "id": "byName", "options": "Instance" }, "properties": [ { "id": "custom.fillOpacity", "value": 21 }, { "id": "custom.lineWidth", "value": 0 }, { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } }, { "id": "custom.gradientMode", "value": "hue" }, { "id": "links", "value": [ { "title": "PGSQL Instance : ${__field.labels.ins}", "url": "/d/pgsql-instance?var-ins=${__field.labels.ins}\ufeff&\ufeff${__url_time_range}" } ] } ] }, { "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "links", "value": [ { "title": "PGSQL Database : ${__field.labels.ins}.${__field.labels.datname}", "url": "/d/pgsql-database?var-ins=${__field.labels.ins}&var-datname=${__field.labels.datname}&${__url_time_range}&viewPanel=137" } ] } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 22 }, "id": 109, "links": [ { "title": "PGRDS Cluster Transaction RT: ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=109&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "8.0.0-beta2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:active_time_rate1m{ins=\"$ins\"} / pg:ins:xact_total_rate1m{ins=\"$ins\"}", "interval": "", "intervalFactor": 1, "legendFormat": "Instance", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:db:active_time_rate1m{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'} / pg:db:xact_total_rate1m{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'}", "hide": false, "interval": "", "intervalFactor": 1, "legendFormat": "{{ datname }}", "range": true, "refId": "B" } ], "title": "Transaction RT (1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Transaction Committed rate in last 1 minute, group on database level", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 20, "gradientMode": "hue", "hideFrom": { "graph": false, "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Xacts for ${__field.labels.ins}", "url": "/d/pgsql-xacts?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "short" }, "overrides": [ { "matcher": { "id": "byName", "options": "Instance" }, "properties": [ { "id": "custom.lineWidth", "value": 0 }, { "id": "custom.fillOpacity", "value": 20 } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 28 }, "id": 94, "links": [ { "title": "PGRDS Cluster TPS: ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=94&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "8.0.0-beta2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:db:xact_commit_rate1m{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'}", "hide": false, "interval": "", "intervalFactor": 1, "legendFormat": "{{ datname }}", "range": true, "refId": "B" } ], "title": "TPS by Database (rate1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Postgres Load Index in last 1 minutes\n\n\nrate(pg_stat_database.active_time[5m])\n\n\nload 1 = 100% usage of one cpu core.\n\n\nCalculated by session active time (PG14+) @ database level.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 40, "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineStyle": { "fill": "solid" }, "lineWidth": 1, "pointSize": 3, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGRDS Instance : ${__field.labels.ins}", "url": "/d/pgrds-instance?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "short" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 28 }, "id": 147, "links": [ { "title": "PGRDS Cluster Load: ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=147&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:db:active_time_rate1m{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'}", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Postgres Load (rate1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Tuples fetched from databases r/s in last minute, group by database on this instance. ", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 3, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGRDS Instance : ${__field.labels.ins}", "url": "/d/pgrds-instance?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "cps" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 34 }, "id": 127, "links": [ { "title": "PGRDS Cluster Row Fetched: ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=127&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "8.0.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (rate(pg_db_tup_fetched{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'}[1m]))", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Row Fetched (rate1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Tuples fetched, inserted, updated, delete r/s in last minute, on this instance", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 50, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 0, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "cps" }, "overrides": [ { "matcher": { "id": "byName", "options": "DELETED" }, "properties": [ { "id": "color", "value": { "fixedColor": "#cc4637d9", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "INSERTED" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "UPDATED" }, "properties": [ { "id": "color", "value": { "fixedColor": "#fcdb72", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 34 }, "id": 128, "links": [ { "title": "PGRDS Cluster Row Modified: ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=128&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "8.0.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum(rate(pg_db_tup_inserted{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'}[1m]))", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "INSERTED", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum(rate(pg_db_tup_updated{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'}[1m]))", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "UPDATED", "range": true, "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum(rate(pg_db_tup_deleted{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'}[1m]))", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "DELETED", "range": true, "refId": "D" } ], "title": "Row Modified (rate1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Table lock categorized in to 3 major categories:\nRead locks, Write Locks, and Exclusive locks.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "bars", "fillOpacity": 100, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": true, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "short" }, "overrides": [ { "matcher": { "id": "byName", "options": "Read" }, "properties": [ { "id": "color", "value": { "fixedColor": "#346f36cc", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Write" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Exclusive" }, "properties": [ { "id": "color", "value": { "fixedColor": "#cc4637d9", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 40 }, "id": 180, "links": [ { "title": "PGRDS Cluster Locks by Category: ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=180&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Name", "sortDesc": true }, "tooltip": { "mode": "single", "sort": "none" } }, "pluginVersion": "8.0.5", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:xlock_count{ins=\"$ins\"}", "interval": "", "legendFormat": "Exclusive", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:wlock_count{ins=\"$ins\"}", "interval": "", "legendFormat": "Write", "range": true, "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:rlock_count{ins=\"$ins\"}", "interval": "", "legendFormat": "Read", "range": true, "refId": "C" } ], "title": "Locks by Category", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Table Locks in 8 different mode, on this instance", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "bars", "fillOpacity": 55, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": true, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "short" }, "overrides": [ { "matcher": { "id": "byName", "options": "AccessShareLock" }, "properties": [ { "id": "color", "value": { "fixedColor": "#56A64B", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "RowShareLock" }, "properties": [ { "id": "color", "value": { "fixedColor": "#8AB8FF", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "RowExclusiveLock" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3274D9", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "ShareUpdateExclusiveLock" }, "properties": [ { "id": "color", "value": { "fixedColor": "#F2CC0C", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "ShareLock" }, "properties": [ { "id": "color", "value": { "fixedColor": "#CC9D00", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "ShareRowExclusiveLock" }, "properties": [ { "id": "color", "value": { "fixedColor": "#FF780A", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "ExclusiveLock" }, "properties": [ { "id": "color", "value": { "fixedColor": "#E02F44", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "AccessExclusiveLock" }, "properties": [ { "id": "color", "value": { "fixedColor": "#A352CC", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 12, "w": 12, "x": 12, "y": 40 }, "id": 154, "links": [ { "title": "PGRDS Cluster Locks : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=154&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "max", "mean", "last" ], "displayMode": "table", "placement": "bottom", "showLegend": true, "sortBy": "Max", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "8.0.5", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (mode) (pg_lock_count{ins=\"$ins\"})", "interval": "", "legendFormat": "{{ mode }}", "range": true, "refId": "A" } ], "title": "Locks", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Very long transactions, max elapse time (y) group by session state. On this postgres instance", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "s" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 46 }, "id": 152, "links": [ { "title": "PGRDS Cluster SAGE: ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=152&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (state) (pg_activity_max_tx_duration{ins=\"$ins\", state!~\"(idle.*|disabled)\"})", "interval": "", "legendFormat": "{{ state }}", "range": true, "refId": "A" } ], "title": "SAGE", "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 52 }, "id": 132, "panels": [], "title": "Session", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Ratio of database session / max allowed sesion per database. the limit is min of max_connections and database's connection limit.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "axisSoftMin": 0, "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 1, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "area" } }, "decimals": 1, "links": [ { "title": "PGSQL Session : ${__field.labels.ins}", "url": "/d/pgsql-session?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "green", "value": 0.1 }, { "color": "yellow", "value": 0.3 }, { "color": "orange", "value": 0.5 }, { "color": "red", "value": 0.7 }, { "color": "#b783af", "value": 0.9 } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 53 }, "id": 90, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=90&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:db:conn_usage{ins=\"$ins\", datname!~'template\\\\d'}", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Connection Usage", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Idle in Transaction backend number of all databases in this instance.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "axisSoftMax": 2, "barAlignment": 0, "drawStyle": "line", "fillOpacity": 71, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "area" } }, "links": [ { "title": "PGSQL Session : ${__field.labels.ins}", "url": "/d/pgsql-session?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 1 } ] } }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 53 }, "id": 159, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=159&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg_activity_count{ins=\"$ins\", state=~\"idle in.*\"})", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Idle in Transaction Backends", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Number of database backend process / session by database.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 60, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Session : ${__field.labels.ins}", "url": "/d/pgsql-session?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 59 }, "id": 96, "links": [ { "title": "PGRDS Cluster Backends: ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=96&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg_activity_count{ins=\"$ins\"})", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Backends", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Newly established session on each database.\nIncrease number of last 1 minute", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 71, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Session : ${__field.labels.ins}", "url": "/d/pgsql-session?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "super-light-green", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 59 }, "id": 153, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=153&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (increase(pg_db_sessions{ins=\"$ins\"}[1m]))", "interval": "", "legendFormat": "{{ ins }}", "range": true, "refId": "A" } ], "title": "New Sessions (increase1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Backend number group by state in this instance.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 60, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGRDS Instance : ${__field.labels.ins}", "url": "/d/pgrds-instance?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "idle" }, "properties": [ { "id": "color", "value": { "fixedColor": "#808080", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 65 }, "id": 149, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=149&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Last", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (state) (pg_activity_count{ins=\"$ins\"})", "interval": "", "legendFormat": "{{ state }}", "range": true, "refId": "A" } ], "title": "Backends by State", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Backend number group by backend type in this instance.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 60, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "client backend" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "autovacuum launcher" }, "properties": [ { "id": "color", "value": { "fixedColor": "#b783af", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "background writer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "checkpointer" }, "properties": [ { "id": "color", "value": { "fixedColor": "super-light-orange", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "logical replication launcher" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "walwriter" }, "properties": [ { "id": "color", "value": { "fixedColor": "#cc4637d9", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "archiver" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "TimescaleDB Background Worker Launcher" }, "properties": [ { "id": "color", "value": { "fixedColor": "#fcdb72", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "NULL" }, "properties": [ { "id": "color", "value": { "fixedColor": "#808080", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 65 }, "id": 150, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=150&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (type) (pg_backend_count{ins=\"$ins\"})", "interval": "", "legendFormat": "{{ type }}", "range": true, "refId": "A" } ], "title": "Backends by Type", "transformations": [ { "id": "renameByRegex", "options": { "regex": "(Value)", "renamePattern": "NULL" } } ], "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Max connection duration group by session time.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "s" }, "overrides": [ { "matcher": { "id": "byName", "options": "idle" }, "properties": [ { "id": "color", "value": { "fixedColor": "#808080", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 71 }, "id": 151, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=151&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (state) (pg_activity_max_conn_duration{ins=\"$ins\", state!=\"idle|disabled\"})", "interval": "", "legendFormat": "{{ state }}", "range": true, "refId": "A" } ], "title": "Max Conn Lifespan", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Backend number group by wait event type in this instance.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 71, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "client backend" }, "properties": [ { "id": "color", "value": { "fixedColor": "#346f36cc", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "autovacuum launcher" }, "properties": [ { "id": "color", "value": { "fixedColor": "#b783af", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "background writer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "checkpointer" }, "properties": [ { "id": "color", "value": { "fixedColor": "super-light-orange", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "logical replication launcher" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "walwriter" }, "properties": [ { "id": "color", "value": { "fixedColor": "super-light-blue", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "idle" }, "properties": [ { "id": "color", "value": { "fixedColor": "rgba(128, 128, 128, 0.5)", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "active" }, "properties": [ { "id": "color", "value": { "fixedColor": "#346f36cc", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "disabled" }, "properties": [ { "id": "color", "value": { "fixedColor": "#fcdb72", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "idle in transaction (aborted)" }, "properties": [ { "id": "color", "value": { "fixedColor": "#b783af", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "idle in transaction" }, "properties": [ { "id": "color", "value": { "fixedColor": "#cc4637d9", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Client" }, "properties": [ { "id": "color", "value": { "fixedColor": "#346f36cc", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "NULL" }, "properties": [ { "id": "color", "value": { "fixedColor": "#808080", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 71 }, "id": 165, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=165&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (event) (pg_wait_count{ins=\"$ins\"})", "interval": "", "legendFormat": "{{ event }}", "range": true, "refId": "A" } ], "title": "Backends by Wait Event", "transformations": [ { "id": "renameByRegex", "options": { "regex": "(Value)", "renamePattern": "NULL" } } ], "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Time spent executing SQL statements in this database", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 9, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "area" } }, "links": [ { "title": "PGSQL Session : ${__field.labels.ins}", "url": "/d/pgsql-session?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "orange", "value": 0.5 }, { "color": "red", "value": 0.7 }, { "color": "purple", "value": 0.9 } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 77 }, "id": 183, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=183&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (rate(pg_db_active_time{ins=\"$ins\"}[1m]))\n/ sum by (datname) (rate(pg_db_session_time{ins=\"$ins\"}[1m]))", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Active% (of Session Time)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Abandon: Number of database sessions to this database that were terminated because connection to the client was lost\n\n\nFatal: Number of database sessions to this database that were terminated by fatal errors\n\n\nKilled: Number of database sessions to this database that were terminated by operator intervention\n\n\navailable on PG 14+", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 71, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Session : ${__field.labels.ins}", "url": "/d/pgsql-session?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "super-light-green", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 77 }, "id": 184, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=184&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Max", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (increase(pg_db_sessions_abandoned{ins=\"$ins\"}[1m]))", "interval": "", "legendFormat": "[Abandoned] : {{ datname }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (increase(pg_db_sessions_fatal{ins=\"$ins\"}[1m]))", "hide": false, "interval": "", "legendFormat": "[Fatal] : {{ datname }}", "range": true, "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (increase(pg_db_sessions_killed{ins=\"$ins\"}[1m]))", "hide": false, "interval": "", "legendFormat": "[Killed] : {{ datname }}", "range": true, "refId": "C" } ], "title": "Sessions Failure in 1m", "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 83 }, "id": 171, "panels": [], "title": "Persist", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Rate of lsn in last minute", "fieldConfig": { "defaults": { "color": { "fixedColor": "#f5a673", "mode": "fixed" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 20, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 3, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGRDS Instance : ${__field.labels.ins}", "url": "/d/pgrds-instance?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "Bps" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 84 }, "id": 207, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=140&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": false }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:ins:lsn_rate1m{ins=\"$ins\"}", "interval": "", "legendFormat": "{{ ins }}", "range": true, "refId": "A" } ], "title": "LSN Progress (rate1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "XID Usage of each databases in this instance.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "area" } }, "links": [ { "title": "PGSQL Database : ${datname}", "url": "/d/pgsql-database?var-ins=${ins}&var-datname=${datname}&${__url_time_range}" } ], "mappings": [ { "options": { "match": "null+nan", "result": { "color": "gray", "index": 0, "text": "\u2205" } }, "type": "special" } ], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "yellow", "value": 2 }, { "color": "orange", "value": 10 }, { "color": "red", "value": 30 }, { "color": "purple", "value": 90 }, { "color": "text", "value": 100 } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 84 }, "id": 208, "links": [ { "title": "PGRDS Cluster Age: ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=161&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Max", "sortDesc": true }, "tooltip": { "mode": "single", "sort": "none" } }, "pluginVersion": "8.0.3", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (datname) (pg:db:age{cls=\"$cls\"})", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Age Usage", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 30, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Persist : ${__field.labels.ins}", "url": "/d/pgsql-persist?var-ins=${__field.labels.ins}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "decbytes" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 90 }, "id": 176, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=176&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Last", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg_size_bytes{ins=\"$ins\"})", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Database Cluster Size", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 8, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Persist : ${__field.labels.ins}", "url": "/d/pgsql-persist?var-ins=${__field.labels.ins}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "decbytes" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 90 }, "id": 178, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=178&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg_size_bytes{ins=\"$ins\", datname=~\"(log|wal)\"})", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Database WAL/Log Size", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 60, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "Scheduled" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Requested" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 96 }, "id": 160, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=160&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "single", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_bgwriter_checkpoints_timed{ins=\"$ins\"}", "hide": false, "interval": "", "legendFormat": "Scheduled", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_bgwriter_checkpoints_req{ins=\"$ins\"} ", "hide": false, "interval": "", "legendFormat": "Requested", "range": true, "refId": "B" } ], "title": "Checkpoint Scheduled / Requested", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 30, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Database for ${__field.labels.datname}", "url": "/d/pgsql-database?var-ins=${ins}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "ms" }, "overrides": [ { "matcher": { "id": "byName", "options": "Sync" }, "properties": [ { "id": "color", "value": { "fixedColor": "#cc4637d9", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Write" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 96 }, "id": 174, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=174&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "rate(pg_bgwriter_checkpoint_sync_time{ins=\"$ins\"}[1m])", "interval": "", "legendFormat": "Sync", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "- rate(pg_bgwriter_checkpoint_write_time{ins=\"$ins\"}[1m])", "hide": false, "interval": "", "legendFormat": "Write", "range": true, "refId": "B" } ], "title": "Checkpoint Time : Sync/Write", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 60, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 0, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "decbytes" }, "overrides": [ { "matcher": { "id": "byName", "options": "Clean" }, "properties": [ { "id": "color", "value": { "fixedColor": "#346f36cc", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Backend" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Checkpoint" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 102 }, "id": 172, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=172&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "rate(pg_bgwriter_buffers_clean{ins=\"$ins\"}[1m]) * 8192", "interval": "", "legendFormat": "Clean", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "rate(pg_bgwriter_buffers_checkpoint{ins=\"$ins\"}[1m]) * 8192", "hide": false, "interval": "", "legendFormat": "Checkpoint", "range": true, "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "rate(pg_bgwriter_buffers_backend{ins=\"$ins\"}[1m]) * 8192", "hide": false, "instant": true, "interval": "", "legendFormat": "Backend", "refId": "C" } ], "title": "BGWriter Buffer Flush", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 60, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 0, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "decbytes" }, "overrides": [ { "matcher": { "id": "byName", "options": "Clean" }, "properties": [ { "id": "color", "value": { "fixedColor": "#346f36cc", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Backend" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Checkpoint" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Alloc" }, "properties": [ { "id": "color", "value": { "fixedColor": "super-light-red", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 102 }, "id": 173, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=173&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "list", "placement": "bottom", "showLegend": false }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "rate(pg_bgwriter_buffers_alloc{ins=\"$ins\"}[1m]) * 8192", "interval": "", "legendFormat": "Alloc", "range": true, "refId": "A" } ], "title": "BGWriter Buffer Alloc", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Database 4k blocks access", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 60, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 0, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Persist : ${__field.labels.ins}", "url": "/d/pgsql-persist?var-ins=${__field.labels.ins}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "decbytes" }, "overrides": [ { "matcher": { "id": "byName", "options": "Clean" }, "properties": [ { "id": "color", "value": { "fixedColor": "#346f36cc", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Backend" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Checkpoint" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 108 }, "id": 179, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=179&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Name", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg:db:blks_access_1m{ins=\"$ins\"}) * 4096", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Blocks Access 1m", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Database 4k blocks hit ratio in last 1 minute", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "axisSoftMax": 1, "barAlignment": 0, "drawStyle": "line", "fillOpacity": 2, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 2, "links": [ { "title": "PGSQL Persist : ${__field.labels.ins}", "url": "/d/pgsql-persist?var-ins=${__field.labels.ins}&var-datname=${__field.labels.datname}&${__url_time_range}\n" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 108 }, "id": 170, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=170&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Max", "sortDesc": false }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:db:blks_hit_ratio1m{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'}", "hide": false, "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Blocks Hit Ratio (1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Database 4k blocks access", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 60, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 0, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Persist : ${__field.labels.ins}", "url": "/d/pgsql-persist?var-ins=${__field.labels.ins}&var-datname=${__field.labels.datname}&${__url_time_range}\n" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "decbytes" }, "overrides": [ { "matcher": { "id": "byName", "options": "Clean" }, "properties": [ { "id": "color", "value": { "fixedColor": "#346f36cc", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Backend" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "Checkpoint" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 114 }, "id": 182, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=182&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Name", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg:db:blks_read_1m{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'}) * 4096", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Blocks Read (1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": true, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 5, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Persist : ${__field.labels.ins}", "url": "/d/pgsql-persist?var-ins=${__field.labels.ins}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 114 }, "id": 181, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=181&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg:db:blk_read_time_seconds_rate1m{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'})", "interval": "", "legendFormat": "\u25b2 {{ datname }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "- sum by (datname) (pg:db:blk_write_time_seconds_rate1m{ins=\"$ins\", datname!~'rdsadmin|polardb_admin|template\\\\d'})", "hide": false, "interval": "", "legendFormat": "\u25bc {{ datname }}", "range": true, "refId": "B" } ], "title": "Blocks Read/Write Time Spent Rate1m", "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 120 }, "id": 162, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=162&${__url_time_range}\n" } ], "panels": [], "title": "Database", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Database size on cluster primary", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 35, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 0, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "decbytes" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 121 }, "id": 175, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=175&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Last", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg_size_bytes{ins=\"$ins\", datname!~'wal|log|template\\\\d'}", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Database Size", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Database Size Change on Cluster Primary in Last 10 minutes", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 35, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "decbytes" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 121 }, "id": 185, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=185&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Last", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "delta(pg_size_bytes{ins=\"$ins\", datname!~'wal|log|template\\\\d'}[10m])", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Database Size Delta 10m", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Database Transaction Commit Rate in last 1 minute", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 20, "gradientMode": "hue", "hideFrom": { "graph": false, "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}\n" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "short" }, "overrides": [ { "matcher": { "id": "byName", "options": "Instance" }, "properties": [ { "id": "custom.lineWidth", "value": 0 }, { "id": "custom.fillOpacity", "value": 20 } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 127 }, "id": 144, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=144&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "8.0.0-beta2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg:db:xact_commit_rate1m{ins=\"$ins\", datname!~'template\\\\d'})", "hide": false, "interval": "", "intervalFactor": 1, "legendFormat": "{{ datname }}", "range": true, "refId": "B" } ], "title": "TPS by Database (rate1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Active processs for each database in this cluster", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 60, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}\n" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 127 }, "id": 148, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=148&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Last", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg_activity_count{ins=\"$ins\"})", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Session by Database", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Database 4k blocks hit ratio in last 1 minute", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "axisSoftMax": 1, "barAlignment": 0, "drawStyle": "line", "fillOpacity": 2, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 2, "links": [ { "title": "PGSQL Database : ${__field.labels.ins}.${__field.labels.datname}", "url": "/d/pgsql-database?var-ins=${__field.labels.ins}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 133 }, "id": 155, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=155&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Max", "sortDesc": false }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:db:blks_hit_ratio1m{ins=\"$ins\", datname!~'template\\\\d'}", "hide": false, "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Database Blocks Hit Ratio (1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "number of dangerous idle in transaction backend in different database amoung this cluster", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "axisSoftMax": 2, "barAlignment": 0, "drawStyle": "line", "fillOpacity": 71, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "area" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}\n" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 1 } ] } }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 133 }, "id": 167, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=167&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Max", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (pg_activity_count{ins=\"$ins\", state=~\"idle in.*\"})", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Idle in Transaction Backends", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Connection used ratio of min(\n max_connections @ instance level,\n connlimit @ database level\n)", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "axisSoftMin": 0, "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 1, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "area" } }, "decimals": 1, "links": [ { "title": "PGSQL Database : ${__field.labels.ins}.${__field.labels.datname}", "url": "/d/pgsql-database?var-ins=${__field.labels.ins}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null }, { "color": "#346f36cc", "value": 0.1 }, { "color": "#fcdb72", "value": 0.3 }, { "color": "#f5a673", "value": 0.5 }, { "color": "red", "value": 0.7 }, { "color": "#b783af", "value": 0.9 } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 139 }, "id": 168, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=168&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:db:conn_usage{ins=\"$ins\", datname!~'template\\\\d'}", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Connection Usage", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 71, "gradientMode": "hue", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineWidth": 0, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Session : ${__field.labels.ins}", "url": "/d/pgsql-session?var-ins=${__field.labels.ins}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "super-light-green", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 139 }, "id": 166, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=166&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Mean", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "increase(pg_db_sessions{ins=\"$ins\", datname!~'template\\\\d'}[1m])", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "New Sessions (incr1m)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Tuples fetched from databases among this cluster", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 3, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}\n" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "cps" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 145 }, "id": 163, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=163&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Mean", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "8.0.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname) (rate(pg_db_tup_fetched{ins=\"$ins\", datname!~'template\\\\d'}[1m]))", "interval": "", "legendFormat": "{{ datname }}", "range": true, "refId": "A" } ], "title": "Row Fetched", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Tuples fetched, inserted, updated, delete among this cluster group by database", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 50, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 0, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}\n" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "cps" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 145 }, "id": 164, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=164&${__url_time_range}\n" } ], "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Mean", "sortDesc": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "8.0.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum(rate(pg_db_tup_inserted{ins=\"$ins\", datname!~'template\\\\d'}[1m])) by (datname)", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "INSERT.{{ datname }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum(rate(pg_db_tup_updated{ins=\"$ins\", datname!~'template\\\\d'}[1m])) by (datname)", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "UPDATE.{{ datname }}", "range": true, "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum(rate(pg_db_tup_deleted{ins=\"$ins\", datname!~'template\\\\d'}[1m])) by (datname)", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "DELETE.{{ datname }}", "range": true, "refId": "D" } ], "title": "Row Modified", "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 151 }, "id": 124, "links": [ { "title": "PGRDS Cluster : ${cls}", "url": "/d/pgrds-cluster?var-cls=${cls}&viewPanel=124&${__url_time_range}\n" } ], "panels": [], "title": "Table & Query", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Index Scan + Seq Scan on this table per second", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 2, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "short" }, "overrides": [] }, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 152 }, "id": 125, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Mean", "sortDesc": true }, "tooltip": { "mode": "single", "sort": "none" } }, "pluginVersion": "8.0.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname,relname) (\n pg:table:scan_rate1m{ins=\"$ins\", datname!~'template\\\\d'}\n)", "interval": "", "intervalFactor": 2, "legendFormat": "{{ datname }}.{{ relname }}", "range": true, "refId": "A" } ], "title": "Table Scan (scan/s)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 2, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "short" }, "overrides": [] }, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 152 }, "id": 126, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Mean", "sortDesc": true }, "tooltip": { "mode": "single", "sort": "none" } }, "pluginVersion": "8.0.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "sum by (datname,relname) (\n rate(pg_table_tup_read{ins=\"$ins\", datname!~'template\\\\d'}[1m])\n)", "interval": "", "intervalFactor": 2, "legendFormat": "{{ datname }}.{{ relname }}", "range": true, "refId": "A" } ], "title": "Tuple Read (rows/s)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "ops" }, "overrides": [] }, "gridPos": { "h": 11, "w": 12, "x": 0, "y": 159 }, "id": 187, "options": { "legend": { "calcs": [ "max", "mean", "last" ], "displayMode": "table", "placement": "bottom", "showLegend": true, "sortBy": "Max", "sortDesc": true }, "tooltip": { "mode": "single", "sort": "none" } }, "pluginVersion": "8.0.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "pg:query:call_rate1m{ins=\"$ins\", datname!~'template\\\\d'}", "interval": "", "intervalFactor": 2, "legendFormat": "{{ datname }}.{{ query }}", "range": true, "refId": "B" } ], "title": "Query Call", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Top Query Time Spent among this cluster", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 40, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 0, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "links": [ { "title": "PGSQL Databases : ${cls}.${__field.labels.datname}", "url": "/d/pgsql-databases?var-cls=${cls}&var-datname=${__field.labels.datname}&${__url_time_range}" } ], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "s" }, "overrides": [] }, "gridPos": { "h": 11, "w": 12, "x": 12, "y": 159 }, "id": 111, "options": { "legend": { "calcs": [ "max", "mean", "last" ], "displayMode": "table", "placement": "bottom", "showLegend": true, "sortBy": "Mean", "sortDesc": true }, "tooltip": { "mode": "single", "sort": "none" } }, "pluginVersion": "8.0.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "rate(pg_query_exec_time{ins=\"$ins\", datname!~'template\\\\d'}[1m])", "interval": "", "intervalFactor": 2, "legendFormat": "{{ datname }}.{{ query }}", "range": true, "refId": "B" } ], "title": "Query Time ", "type": "timeseries" } ], "refresh": "", "revision": 1, "schemaVersion": 39, "tags": [ "Pigsty", "PGSQL", "Instance", "PGRDS" ], "templating": { "list": [ { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_up, ins)", "description": "Unique instance identifier (e.g pg-meta-1)", "hide": 0, "includeAll": false, "label": "Instance", "multi": false, "name": "ins", "options": [], "query": { "query": "label_values(pg_up, ins)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_up{ins=\"$ins\"} , ip)", "description": "IP address of this postgres instance", "hide": 2, "includeAll": false, "label": "IP", "multi": false, "name": "ip", "options": [], "query": { "query": "label_values(pg_up{ins=\"$ins\"} , ip)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_up{ins=\"$ins\"}, ins)", "description": "Sequence number of this instance, which is an unique integer among a postgres cluster", "hide": 2, "includeAll": false, "label": "Sequence", "multi": false, "name": "seq", "options": [], "query": { "query": "label_values(pg_up{ins=\"$ins\"}, ins)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "/^[a-zA-Z0-9-_]+-(\\d+)$/", "skipUrlSync": false, "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_up{ins=\"$ins\"}, cls)", "description": "Cluster identifier for this postgres instance, cls should be unique among entire environment. such as pg-meta, pg-test", "hide": 2, "includeAll": false, "label": "Cluster", "multi": false, "name": "cls", "options": [], "query": { "query": "label_values(pg_up{ins=\"$ins\"}, cls)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(node_uname_info{ip=\"$ip\"},nodename)", "description": "Node name of current postgres instance", "hide": 2, "includeAll": false, "label": "Node", "multi": false, "name": "node", "options": [], "query": { "query": "label_values(node_uname_info{ip=\"$ip\"},nodename)", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_db_age{ins=\"$ins\", datname!~\"template0|template1|postgres|rdsadmin|polardb_admin\"},datname)", "description": "Non-trivial database in this instance", "hide": 2, "includeAll": false, "label": "Database", "multi": false, "name": "datname", "options": [], "query": { "query": "label_values(pg_db_age{ins=\"$ins\", datname!~\"template0|template1|postgres|rdsadmin|polardb_admin\"},datname)", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 1, "type": "query" } ] }, "time": { "from": "now-1h", "to": "now" }, "timepicker": {}, "timezone": "", "title": "PGRDS Instance", "uid": "pgrds-instance", "version": 1, "weekStart": "" }pg_exporter-1.4.1/monitor/pgsql-exporter.json000066400000000000000000003756411523237665500214670ustar00rootroot00000000000000{ "annotations": { "list": [ { "builtIn": 1, "datasource": { "type": "datasource", "uid": "grafana" }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "target": { "limit": 100, "matchAny": false, "tags": [], "type": "dashboard" }, "type": "dashboard" } ] }, "author": "Ruohang Feng (rh@vonng.com)", "description": "PostgreSQL Instance Dashboard", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": null, "license": "AGPLv3 @ https://pigsty.io/docs/about/license", "links": [ { "asDropdown": true, "icon": "external link", "includeVars": true, "keepTime": true, "tags": [ "Pigsty", "PGSQL", "Overview" ], "targetBlank": false, "title": "Overview", "tooltip": "", "type": "dashboards", "url": "" }, { "asDropdown": true, "icon": "external link", "includeVars": true, "keepTime": true, "tags": [ "Pigsty", "PGSQL", "Cluster" ], "targetBlank": false, "title": "Cluster", "tooltip": "", "type": "dashboards", "url": "" }, { "asDropdown": true, "icon": "external link", "includeVars": true, "keepTime": true, "tags": [ "Pigsty", "PGSQL", "Instance" ], "targetBlank": false, "title": "Instance", "tooltip": "", "type": "dashboards", "url": "" }, { "asDropdown": true, "icon": "external link", "includeVars": true, "keepTime": true, "tags": [ "Pigsty", "PGSQL", "Database" ], "targetBlank": false, "title": "Database", "tooltip": "", "type": "dashboards", "url": "" } ], "panels": [ { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 137, "panels": [], "title": "Overview", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "max": 2.5, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#3e668f", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "Cluster" }, "properties": [ { "id": "displayName", "value": "${cls}" }, { "id": "mappings", "value": [ { "options": { "1": { "index": 0, "text": "Cluster" } }, "type": "value" } ] }, { "id": "links", "value": [ { "title": "PGSQL Cluster : ${cls}", "url": "/d/pgsql-cluster?var-cls=${cls}&${__url_time_range}" } ] } ] }, { "matcher": { "id": "byName", "options": "Instance" }, "properties": [ { "id": "displayName", "value": "${ins}" }, { "id": "mappings", "value": [ { "options": { "1": { "index": 0, "text": "Instance" } }, "type": "value" } ] }, { "id": "links", "value": [ { "title": "PGCAT Instance : ${ins}", "url": "/d/pgcat-instance?var-dsn=${ins}.${datname}" } ] } ] }, { "matcher": { "id": "byName", "options": "IP" }, "properties": [ { "id": "mappings", "value": [ { "options": { "1": { "index": 0, "text": "Node IP" } }, "type": "value" } ] }, { "id": "displayName", "value": "${ip}" }, { "id": "links", "value": [ { "title": "Node Instance : ${ip}", "url": "/d/node-instance?var-id=${ip}&${__url_time_range}" } ] } ] }, { "matcher": { "id": "byName", "options": "Name" }, "properties": [ { "id": "displayName", "value": "${node}" }, { "id": "mappings", "value": [ { "options": { "1": { "index": 0, "text": "Hostname" } }, "type": "value" } ] }, { "id": "links", "value": [ { "title": "Node Instance : ${node}", "url": "/d/node-instance?var-id=${node}&${__url_time_range}" } ] } ] } ] }, "gridPos": { "h": 6, "w": 6, "x": 0, "y": 1 }, "id": 110, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "text": { "titleSize": 20, "valueSize": 16 }, "textMode": "value_and_name", "wideLayout": true }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "1", "hide": false, "instant": true, "interval": "", "legendFormat": "Instance", "queryType": "measurements", "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "1", "hide": false, "instant": true, "interval": "", "legendFormat": "Cluster", "queryType": "measurements", "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "1", "hide": false, "instant": true, "interval": "", "legendFormat": "IP", "queryType": "measurements", "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "1", "hide": false, "instant": true, "interval": "", "legendFormat": "Name", "queryType": "measurements", "refId": "D" } ], "title": "", "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Exporter Aliveness Status in ${cls}\n\nInstance: Goto PGSQL Instance\n\nIP: Goto PGSQL Node\n\nStatus: Goto PGSQL Service\n\nLoad: max(cpu,postgres,pgbouncer)\n\nSpace: Disk space usage max(all device)\n\nProxy: session number, Goto Haproxy Admin Page", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "custom": { "align": "center", "cellOptions": { "type": "auto" }, "inspect": false }, "mappings": [], "max": 1.2, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#e3e3e3e0", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byRegexp", "options": "/postgres|pgbouncer|pgbackrest/" }, "properties": [ { "id": "mappings", "value": [ { "options": { "0": { "color": "#cc4637d9", "index": 0, "text": "DOWN" }, "1": { "color": "#346f36cc", "index": 1, "text": "UP" } }, "type": "value" } ] }, { "id": "custom.cellOptions", "value": { "type": "color-background" } }, { "id": "custom.width", "value": 100 } ] }, { "matcher": { "id": "byRegexp", "options": "/.*up/" }, "properties": [ { "id": "unit", "value": "s" }, { "id": "custom.width", "value": 100 } ] }, { "matcher": { "id": "byName", "options": "Instance" }, "properties": [ { "id": "links", "value": [ { "title": "PGSQL Exporter : ${__data.fields.Instance}", "url": "/d/pgsql-exporter?var-ins=${__data.fields.Instance}&${__url_time_range}" } ] } ] } ] }, "gridPos": { "h": 6, "w": 10, "x": 6, "y": 1 }, "id": 144, "links": [ { "title": "PGSQL Instance : ${primary}", "url": "/d/pgsql-instance?var-ins=${primary}&${__url_time_range}" } ], "options": { "cellHeight": "sm", "footer": { "countRows": false, "fields": "", "reducer": [ "sum" ], "show": false }, "showHeader": true, "sortBy": [ { "desc": false, "displayName": "pg uptime" } ] }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pg_exporter_agent_up{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pgbouncer_exporter_agent_up{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "__auto", "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pgbackrest_exporter_agent_up{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pg_exporter_uptime{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "F" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pgbouncer_exporter_uptime{cls=\"$cls\"})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "G" } ], "title": "Export Status", "transformations": [ { "id": "seriesToColumns", "options": { "byField": "ins", "mode": "outer" } }, { "id": "organize", "options": { "excludeByName": { "Time": true, "Time 1": true, "Time 10": true, "Time 2": true, "Time 3": true, "Time 4": true, "Time 6": true, "Time 8": true, "Time 9": true, "Value #A": false, "Value #B": false, "Value #H": false, "__name__": true, "__name__ 1": true, "__name__ 2": true, "__name__ 3": true, "__name__ 5": true, "__name__ 7": true, "cls": true, "cls 1": true, "cls 2": true, "cls 3": true, "cls 4": true, "cls 5": true, "cls 6": true, "cls 7": true, "instance": true, "instance 1": true, "instance 2": false, "instance 3": true, "instance 4": true, "instance 5": true, "instance 6": true, "ip 1": true, "ip 2": true, "ip 3": true, "ip 4": true, "ip 5": true, "ip 6": true, "ip 7": true, "job": true, "job 1": true, "job 2": true, "job 3": true, "job 4": true, "job 5": true, "job 6": true, "job 7": true }, "includeByName": {}, "indexByName": { "Time 1": 5, "Time 2": 6, "Time 3": 7, "Time 4": 8, "Value #A": 1, "Value #B": 2, "Value #C": 3, "Value #D": 4, "ins": 0 }, "renameByName": { "Time 4": "", "Value #A": "postgres", "Value #B": "pgbouncer", "Value #C": "pgbackrest", "Value #D": "pgb uptime", "Value #E": "DB Conn", "Value #F": "pg uptime", "Value #G": "pgb uptime", "Value #H": "LB", "Value #I": "QPS", "Value #J": "LB Clients", "Value #K": "Lag", "cls 1": "", "cls 2": "", "ins": "Instance", "instance": "", "instance 2": "", "ip": "IP", "ip 1": "IP" } } } ], "type": "table" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 12, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "links": [], "mappings": [ { "options": { "0": { "index": 0, "text": "Dead" }, "1": { "index": 1, "text": "UP" } }, "type": "value" } ], "max": 1.2, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [ { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "postgres" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 8, "x": 16, "y": 1 }, "id": 145, "options": { "legend": { "calcs": [ "min" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Min", "sortDesc": false }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_exporter_agent_up{ins=\"$ins\"}", "legendFormat": "pg_exporter", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbouncer_exporter_agent_up{ins=\"$ins\"}", "legendFormat": "pgbouncer_exporter", "range": true, "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_up{ins=\"$ins\"}", "hide": false, "legendFormat": "postgres", "range": true, "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbouncer_up{ins=\"$ins\"}", "hide": false, "legendFormat": "pgbouncer", "range": true, "refId": "D" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "patroni_up{ins=\"$ins\"}", "hide": false, "legendFormat": "patroni", "range": true, "refId": "E" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbackrest_exporter_agent_up{ins=\"$ins\"}", "hide": false, "legendFormat": "pgbackrest_exporter", "range": true, "refId": "F" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "up{ins=\"$ins\", job=~\"pgsql|pgrds\"}", "hide": true, "legendFormat": "{{ instance }}", "range": true, "refId": "Z" } ], "title": "Aliveness", "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 7 }, "id": 142, "panels": [], "title": "Global Status", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "links": [], "mappings": [ { "options": { "0": { "index": 0, "text": "Dead" }, "1": { "index": 1, "text": "UP" } }, "type": "value" } ], "max": 1.2, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [ { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "postgres" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 8 }, "id": 139, "options": { "legend": { "calcs": [ "min" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Min", "sortDesc": false }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_exporter_agent_up{}", "legendFormat": "{{ instance }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbouncer_exporter_agent_up{}", "legendFormat": "{{ instance }}", "range": true, "refId": "B" } ], "title": "Aliveness", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "All instances among pgsql cluster ${cls}\n\nInstance: Goto PGSQL Instance\n\nIP: Goto PGSQL Node\n\nStatus: Goto PGSQL Service\n\nLoad: max(cpu,postgres,pgbouncer)\n\nSpace: Disk space usage max(all device)\n\nProxy: session number, Goto Haproxy Admin Page", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "custom": { "align": "center", "cellOptions": { "type": "auto" }, "inspect": false }, "mappings": [], "max": 1.2, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#e3e3e3e0", "value": null } ] } }, "overrides": [ { "matcher": { "id": "byRegexp", "options": "/pg_exporter|pgb_exporter/" }, "properties": [ { "id": "mappings", "value": [ { "options": { "0": { "color": "#cc4637d9", "index": 0, "text": "DOWN" }, "1": { "color": "#346f36cc", "index": 1, "text": "UP" } }, "type": "value" } ] }, { "id": "custom.cellOptions", "value": { "type": "color-background" } }, { "id": "custom.width", "value": 110 } ] }, { "matcher": { "id": "byRegexp", "options": "/.*up/" }, "properties": [ { "id": "unit", "value": "s" }, { "id": "custom.width", "value": 100 } ] }, { "matcher": { "id": "byName", "options": "Instance" }, "properties": [ { "id": "links", "value": [ { "title": "PGSQL Exporter : ${__data.fields.Instance}", "url": "/d/pgsql-exporter?var-ins=${__data.fields.Instance}&${__url_time_range}" } ] } ] } ] }, "gridPos": { "h": 12, "w": 12, "x": 12, "y": 8 }, "id": 141, "links": [ { "title": "PGSQL Instance : ${primary}", "url": "/d/pgsql-instance?var-ins=${primary}&${__url_time_range}" } ], "options": { "cellHeight": "sm", "footer": { "countRows": false, "fields": "", "reducer": [ "sum" ], "show": false }, "showHeader": true, "sortBy": [ { "desc": false, "displayName": "pg uptime" } ] }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pg_exporter_agent_up{})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pgbouncer_exporter_agent_up{})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pg_exporter_uptime{})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "exemplar": false, "expr": "max by (ins) (pgbouncer_exporter_uptime{})", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "D" } ], "title": "", "transformations": [ { "id": "seriesToColumns", "options": { "byField": "ins", "mode": "outer" } }, { "id": "organize", "options": { "excludeByName": { "Time": true, "Time 1": true, "Time 10": true, "Time 2": true, "Time 3": true, "Time 4": true, "Time 6": true, "Time 8": true, "Time 9": true, "Value #A": false, "Value #B": false, "Value #H": false, "__name__": true, "__name__ 1": true, "__name__ 2": true, "__name__ 3": true, "__name__ 5": true, "__name__ 7": true, "cls": true, "cls 1": true, "cls 2": true, "cls 3": true, "cls 4": true, "cls 5": true, "cls 6": true, "cls 7": true, "instance": true, "instance 1": true, "instance 2": false, "instance 3": true, "instance 4": true, "instance 5": true, "instance 6": true, "ip 1": true, "ip 2": true, "ip 3": true, "ip 4": true, "ip 5": true, "ip 6": true, "ip 7": true, "job": true, "job 1": true, "job 2": true, "job 3": true, "job 4": true, "job 5": true, "job 6": true, "job 7": true }, "indexByName": { "Time 1": 5, "Time 2": 6, "Time 3": 7, "Time 4": 8, "Value #A": 1, "Value #B": 2, "Value #C": 3, "Value #D": 4, "ins": 0 }, "renameByName": { "Time 4": "", "Value #A": "pg_exporter", "Value #B": "pgb_exporter", "Value #C": "pg uptime", "Value #D": "pgb uptime", "Value #E": "DB Conn", "Value #F": "RT", "Value #G": "LB Conn", "Value #H": "LB", "Value #I": "QPS", "Value #J": "LB Clients", "Value #K": "Lag", "cls 1": "", "cls 2": "", "ins": "Instance", "instance": "", "instance 2": "", "ip": "IP", "ip 1": "IP" } } } ], "type": "table" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "points", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 0, "pointSize": 2, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "s" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 14 }, "id": 138, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Mean", "sortDesc": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_exporter_scrape_duration{}", "legendFormat": "{{ ins }}", "range": true, "refId": "A" } ], "title": "Scrape Duration", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "links": [], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "s" }, "overrides": [ { "matcher": { "id": "byName", "options": "postgres" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 20 }, "id": 146, "options": { "legend": { "calcs": [ "last" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Last", "sortDesc": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_exporter_uptime{}", "legendFormat": "{{ ins }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbouncer_exporter_uptime{}", "hide": false, "legendFormat": "{{ ins }}", "range": true, "refId": "B" } ], "title": "Global Uptime", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Error Increase in last 1 minute", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 12, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [ { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "postgres" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 20 }, "id": 147, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Name", "sortDesc": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pg_exporter_scrape_error_count{}[1m])", "legendFormat": "{{ ins }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pgbouncer_exporter_scrape_error_count{}[1m])", "legendFormat": "{{ ins }}", "range": true, "refId": "B" } ], "title": "Global Error Rate", "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 26 }, "id": 130, "panels": [], "title": "Metrics", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "links": [], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "s" }, "overrides": [ { "matcher": { "id": "byName", "options": "postgres" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 5, "w": 12, "x": 0, "y": 27 }, "id": 124, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_exporter_uptime{ins=\"$ins\"}", "legendFormat": "postgres", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbouncer_exporter_uptime{ins=\"$ins\"}", "hide": false, "legendFormat": "pgbouncer", "range": true, "refId": "B" } ], "title": "Up Time", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 12, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "links": [], "mappings": [ { "options": { "0": { "index": 0, "text": "Dead" }, "1": { "index": 1, "text": "UP" } }, "type": "value" } ], "max": 1.2, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [ { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "postgres" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 5, "w": 12, "x": 12, "y": 27 }, "id": 128, "options": { "legend": { "calcs": [ "min" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Min", "sortDesc": false }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_exporter_agent_up{ins=\"$ins\"}", "legendFormat": "postgres", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbouncer_exporter_agent_up{ins=\"$ins\"}", "legendFormat": "pgbouncer", "range": true, "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbackrest_exporter_agent_up{ins=\"$ins\"}", "hide": false, "legendFormat": "pgbackrest", "range": true, "refId": "D" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "up{ins=\"$ins\", job=~\"pgsql|pgrds\"}", "hide": true, "legendFormat": "{{ instance }}", "range": true, "refId": "C" } ], "title": "Exporter Aliveness", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "s" }, "overrides": [ { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "postgres" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 5, "w": 12, "x": 0, "y": 32 }, "id": 129, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_exporter_scrape_duration{ins=\"$ins\"}", "legendFormat": "postgres", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbouncer_exporter_scrape_duration{ins=\"$ins\"}", "hide": false, "legendFormat": "pgbouncer", "range": true, "refId": "B" } ], "title": "Scrape Duration", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Error Increase in last 1 minute", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 12, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [ { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "postgres" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 5, "w": 12, "x": 12, "y": 32 }, "id": 136, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Name", "sortDesc": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pg_exporter_scrape_error_count{ins=\"$ins\"}[1m])", "legendFormat": "postgres", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pgbouncer_exporter_scrape_error_count{ins=\"$ins\"}[1m])", "legendFormat": "pgbouncer", "range": true, "refId": "B" } ], "title": "Errors Count Per Minute", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "s" }, "overrides": [ { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 5, "w": 12, "x": 0, "y": 37 }, "id": 125, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_exporter_server_scrape_duration{ins=\"$ins\"}", "legendFormat": "{{ datname }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbouncer_exporter_server_scrape_duration{ins=\"$ins\"}", "hide": false, "legendFormat": "pgbouncer", "range": true, "refId": "B" } ], "title": "Scrape Duration (per Server)", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "Scrape Count", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 12, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 1, "links": [], "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [ { "matcher": { "id": "byName", "options": "Error" }, "properties": [ { "id": "color", "value": { "fixedColor": "#E02F44", "mode": "fixed" } }, { "id": "unit", "value": "short" }, { "id": "min", "value": 0 }, { "id": "max", "value": 0.1 } ] } ] }, "gridPos": { "h": 5, "w": 12, "x": 12, "y": 37 }, "id": 126, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pg_exporter_server_scrape_total_count{ins=\"$ins\"}[1m])", "legendFormat": "{{ datname }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pgbouncer_exporter_server_scrape_total_count{ins=\"$ins\"}[1m])", "legendFormat": "pgbouncer", "range": true, "refId": "B" } ], "title": "Scrape Count Per Minute", "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 42 }, "id": 134, "panels": [], "title": "Collectors", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "line+area" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "transparent", "value": null } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 0, "y": 43 }, "id": 123, "options": { "legend": { "calcs": [ "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Max", "sortDesc": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "sum by (query) (increase(pg_exporter_query_scrape_error_count{ins=\"$ins\"}[1m]))", "legendFormat": "{{query}}", "range": true, "refId": "A" } ], "title": "Query Errors", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 2, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 6, "w": 12, "x": 12, "y": 43 }, "id": 132, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Mean", "sortDesc": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "sum by (query) (pg_exporter_query_scrape_metric_count{ins=\"$ins\"})", "legendFormat": "{{query}}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "sum by (query) (pgbouncer_exporter_query_scrape_metric_count{ins=\"$ins\"})", "hide": false, "legendFormat": "{{query}}", "range": true, "refId": "B" } ], "title": "Metrics Count", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 3, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "s" }, "overrides": [] }, "gridPos": { "h": 15, "w": 24, "x": 0, "y": 49 }, "id": 131, "options": { "legend": { "calcs": [ "mean", "lastNotNull", "max" ], "displayMode": "table", "placement": "right", "showLegend": true, "sortBy": "Mean", "sortDesc": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pg_exporter_query_scrape_duration{ins=\"$ins\"}[1m]) / \nincrease(pg_exporter_query_scrape_total_count{ins=\"$ins\"}[1m])", "legendFormat": "{{ datname }}.{{ query }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pgbouncer_exporter_query_scrape_duration{ins=\"$ins\"}[1m]) / \nincrease(pgbouncer_exporter_query_scrape_total_count{ins=\"$ins\"}[1m])", "hide": false, "legendFormat": "pgbouncer.{{ query }}", "range": true, "refId": "B" } ], "title": "Query Duration", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "links": [], "mappings": [], "max": 1, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 9, "w": 12, "x": 0, "y": 64 }, "id": 133, "options": { "legend": { "calcs": [ "mean", "lastNotNull", "max" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pg_exporter_query_scrape_hit_count{ins=\"$ins\"}[5m]) / \nincrease(pg_exporter_query_scrape_total_count{ins=\"$ins\"}[5m])", "legendFormat": "{{ datname }}.{{query}}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "increase(pgbouncer_exporter_query_scrape_hit_count{ins=\"$ins\"}[5m]) / \nincrease(pgbouncer_exporter_query_scrape_total_count{ins=\"$ins\"}[5m])", "hide": false, "legendFormat": "{{ datname }}.{{query}}", "range": true, "refId": "B" } ], "title": "Query Cache Hit Rate", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 1, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepAfter", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "s" }, "overrides": [ { "matcher": { "id": "byName", "options": "pgbouncer" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 9, "w": 12, "x": 12, "y": 64 }, "id": 135, "options": { "legend": { "calcs": [ "mean" ], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pg_exporter_query_cache_ttl{ins=\"$ins\"}", "legendFormat": "{{ datname }}.{{ query }}", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "editorMode": "code", "expr": "pgbouncer_exporter_query_cache_ttl{ins=\"$ins\"}", "hide": false, "legendFormat": "pgbouncer. {{ query }}", "range": true, "refId": "B" } ], "title": "Cache TTL", "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 73 }, "id": 98, "panels": [], "title": "PG Exporter Logs: ${ins}", "type": "row" }, { "datasource": { "type": "loki", "uid": "ds-loki" }, "description": "pg_exporter logs count", "fieldConfig": { "defaults": { "color": { "fixedColor": "#346f36cc", "mode": "fixed" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "bars", "fillOpacity": 100, "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": true, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "#346f36cc", "value": null } ] }, "unit": "none" }, "overrides": [ { "matcher": { "id": "byFrameRefID", "options": "A" }, "properties": [ { "id": "color", "value": { "fixedColor": "#346f36cc", "mode": "fixed" } } ] }, { "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "color", "value": { "fixedColor": "#3e668f", "mode": "fixed" } } ] }, { "matcher": { "id": "byFrameRefID", "options": "C" }, "properties": [ { "id": "color", "value": { "fixedColor": "#f5a673", "mode": "fixed" } } ] }, { "matcher": { "id": "byName", "options": "pgbackrest" }, "properties": [ { "id": "color", "value": { "fixedColor": "light-red", "mode": "fixed" } } ] } ] }, "gridPos": { "h": 5, "w": 24, "x": 0, "y": 74 }, "id": 100, "interval": "1s", "links": [ { "title": "PG Exporter Logs for ${ins}", "url": "/d/logs-instance?var-ins=$ins&var-src=syslog&var-search=exporter&${__url_time_range}" } ], "options": { "legend": { "calcs": [ "sum" ], "displayMode": "table", "placement": "right", "showLegend": false }, "tooltip": { "hideZeros": false, "mode": "single", "sort": "none" } }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "loki", "uid": "ds-loki" }, "editorMode": "code", "expr": "count_over_time(({ip=\"$ip\", src=\"syslog\"} |~ \"pg_exporter\")[$__interval])", "legendFormat": "", "queryType": "range", "refId": "A" } ], "title": "Logs per $__interval", "type": "timeseries" }, { "datasource": { "type": "loki", "uid": "ds-loki" }, "description": "Recent logs for pgbouncer & patroni & postgres", "fieldConfig": { "defaults": {}, "overrides": [] }, "gridPos": { "h": 12, "w": 24, "x": 0, "y": 79 }, "id": 102, "options": { "dedupStrategy": "none", "enableInfiniteScrolling": false, "enableLogDetails": true, "prettifyLogMessage": false, "showCommonLabels": false, "showLabels": false, "showTime": true, "sortOrder": "Descending", "wrapLogMessage": false }, "pluginVersion": "11.5.2", "targets": [ { "datasource": { "type": "loki", "uid": "ds-loki" }, "editorMode": "code", "expr": "{ip=\"$ip\"} |~ \"pg_exporter\"", "instant": false, "queryType": "range", "range": true, "refId": "A" } ], "title": "Recent Logs", "type": "logs" } ], "preload": false, "refresh": "", "schemaVersion": 40, "tags": [ "Pigsty", "PGSQL", "Instance" ], "templating": { "list": [ { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_up, ins)", "description": "Unique instance identifier (e.g pg-meta-1)", "includeAll": false, "label": "Instance", "name": "ins", "options": [], "query": { "query": "label_values(pg_up, ins)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "", "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_up{ins=\"$ins\"} , ip)", "description": "IP address of this postgres instance", "hide": 2, "includeAll": false, "label": "IP", "name": "ip", "options": [], "query": { "query": "label_values(pg_up{ins=\"$ins\"} , ip)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "", "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_up{ins=\"$ins\"}, ins)", "description": "Sequence number of this instance, which is an unique integer among a postgres cluster", "hide": 2, "includeAll": false, "label": "Sequence", "name": "seq", "options": [], "query": { "query": "label_values(pg_up{ins=\"$ins\"}, ins)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "/^[a-zA-Z0-9-_]+-(\\d+)$/", "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_up{ins=\"$ins\"}, cls)", "description": "Cluster identifier for this postgres instance, cls should be unique among entire environment. such as pg-meta, pg-test", "hide": 2, "includeAll": false, "label": "Cluster", "name": "cls", "options": [], "query": { "query": "label_values(pg_up{ins=\"$ins\"}, cls)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "", "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(node_uname_info{ip=\"$ip\"},nodename)", "description": "Node name of current postgres instance", "hide": 2, "includeAll": false, "label": "Node", "name": "node", "options": [], "query": { "query": "label_values(node_uname_info{ip=\"$ip\"},nodename)", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 1, "regex": "", "sort": 1, "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "ds-prometheus" }, "definition": "label_values(pg_db_age{ins=\"$ins\",datname!~'postgres|template0|template1'}, datname)", "description": "Non-trivial database in this instance", "hide": 2, "includeAll": false, "label": "Database", "name": "datname", "options": [], "query": { "query": "label_values(pg_db_age{ins=\"$ins\",datname!~'postgres|template0|template1'}, datname)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "", "sort": 1, "type": "query" } ] }, "time": { "from": "now-1h", "to": "now" }, "timepicker": {}, "timezone": "Asia/Shanghai", "title": "PGSQL Exporter", "uid": "pgsql-exporter", "version": 1, "weekStart": "" }pg_exporter-1.4.1/package/000077500000000000000000000000001523237665500154645ustar00rootroot00000000000000pg_exporter-1.4.1/package/nfpm-amd64-deb.yaml000066400000000000000000000023531523237665500207540ustar00rootroot00000000000000name: "pg-exporter" arch: "amd64" platform: "linux" version: "v1.4.1" release: "1" version_schema: semver maintainer: Ruohang Feng description: | Prometheus exporter for PostgreSQL / Pgbouncer server metrics. Supported version: Postgres9.x - 19+ & Pgbouncer 1.8 - 1.25+ Part of Project Pigsty -- Battery Included PostgreSQL Distribution with ultimate observability support: https://pigsty.io/docs/pg_exporter vendor: "PGSTY" homepage: "https://pigsty.io/docs/pg_exporter" license: "Apache-2.0 License" rpm: compression: gzip prefixes: - /usr/bin contents: - src: pg_exporter dst: /usr/bin/pg_exporter file_info: mode: 0755 - src: pg_exporter.yml dst: /etc/pg_exporter.yml type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.default dst: /etc/default/pg_exporter type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.service dst: /lib/systemd/system/pg_exporter.service type: config - src: LICENSE dst: /usr/share/doc/pg_exporter/LICENSE file_info: mode: 0644 scripts: preinstall: package/preinstall.sh pg_exporter-1.4.1/package/nfpm-amd64-rpm.yaml000066400000000000000000000024441523237665500210210ustar00rootroot00000000000000name: "pg-exporter" arch: "amd64" platform: "linux" version: "v1.4.1" release: "1" version_schema: semver maintainer: Ruohang Feng description: | Prometheus exporter for PostgreSQL / Pgbouncer server metrics. Supported version: Postgres9.x - 19+ & Pgbouncer 1.8 - 1.25+ Part of Project Pigsty -- Battery Included PostgreSQL Distribution with ultimate observability support: https://pigsty.io/docs/pg_exporter vendor: "PGSTY" homepage: "https://pigsty.io/docs/pg_exporter" license: "Apache-2.0 License" provides: - pg_exporter replaces: - pg_exporter rpm: compression: gzip prefixes: - /usr/bin contents: - src: pg_exporter dst: /usr/bin/pg_exporter file_info: mode: 0755 - src: pg_exporter.yml dst: /etc/pg_exporter.yml type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.default dst: /etc/default/pg_exporter type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.service dst: /usr/lib/systemd/system/pg_exporter.service type: config - src: LICENSE dst: /usr/share/doc/pg_exporter/LICENSE file_info: mode: 0644 scripts: preinstall: package/preinstall.sh pg_exporter-1.4.1/package/nfpm-arm64-deb.yaml000066400000000000000000000023531523237665500207720ustar00rootroot00000000000000name: "pg-exporter" arch: "arm64" platform: "linux" version: "v1.4.1" release: "1" version_schema: semver maintainer: Ruohang Feng description: | Prometheus exporter for PostgreSQL / Pgbouncer server metrics. Supported version: Postgres9.x - 19+ & Pgbouncer 1.8 - 1.25+ Part of Project Pigsty -- Battery Included PostgreSQL Distribution with ultimate observability support: https://pigsty.io/docs/pg_exporter vendor: "PGSTY" homepage: "https://pigsty.io/docs/pg_exporter" license: "Apache-2.0 License" rpm: compression: gzip prefixes: - /usr/bin contents: - src: pg_exporter dst: /usr/bin/pg_exporter file_info: mode: 0755 - src: pg_exporter.yml dst: /etc/pg_exporter.yml type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.default dst: /etc/default/pg_exporter type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.service dst: /lib/systemd/system/pg_exporter.service type: config - src: LICENSE dst: /usr/share/doc/pg_exporter/LICENSE file_info: mode: 0644 scripts: preinstall: package/preinstall.sh pg_exporter-1.4.1/package/nfpm-arm64-rpm.yaml000066400000000000000000000024441523237665500210370ustar00rootroot00000000000000name: "pg-exporter" arch: "arm64" platform: "linux" version: "v1.4.1" release: "1" version_schema: semver maintainer: Ruohang Feng description: | Prometheus exporter for PostgreSQL / Pgbouncer server metrics. Supported version: Postgres9.x - 19+ & Pgbouncer 1.8 - 1.25+ Part of Project Pigsty -- Battery Included PostgreSQL Distribution with ultimate observability support: https://pigsty.io/docs/pg_exporter vendor: "PGSTY" homepage: "https://pigsty.io/docs/pg_exporter" license: "Apache-2.0 License" provides: - pg_exporter replaces: - pg_exporter rpm: compression: gzip prefixes: - /usr/bin contents: - src: pg_exporter dst: /usr/bin/pg_exporter file_info: mode: 0755 - src: pg_exporter.yml dst: /etc/pg_exporter.yml type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.default dst: /etc/default/pg_exporter type: config|noreplace file_info: mode: 0700 owner: prometheus group: prometheus - src: package/pg_exporter.service dst: /usr/lib/systemd/system/pg_exporter.service type: config - src: LICENSE dst: /usr/share/doc/pg_exporter/LICENSE file_info: mode: 0644 scripts: preinstall: package/preinstall.sh pg_exporter-1.4.1/package/pg_exporter.default000066400000000000000000000007101523237665500213660ustar00rootroot00000000000000PG_EXPORTER_URL='postgres://:5432/postgres?sslmode=disable' PG_EXPORTER_CONFIG=/etc/pg_exporter.yml PG_EXPORTER_LABEL="" PG_EXPORTER_TAG="" PG_EXPORTER_DISABLE_CACHE=false PG_EXPORTER_AUTO_DISCOVERY=true PG_EXPORTER_EXCLUDE_DATABASE="template0,template1,postgres" PG_EXPORTER_INCLUDE_DATABASE="" PG_EXPORTER_NAMESPACE="pg" PG_EXPORTER_FAIL_FAST=false PG_EXPORTER_CONNECT_TIMEOUT=100 PG_EXPORTER_TELEMETRY_PATH="/metrics" PG_EXPORTER_OPTS='--log.level=info'pg_exporter-1.4.1/package/pg_exporter.service000066400000000000000000000005031523237665500214020ustar00rootroot00000000000000[Unit] Description=Prometheus exporter for PostgreSQL/Pgbouncer server metrics Documentation=https://pigsty.io/docs/pg_exporter After=network.target [Service] EnvironmentFile=-/etc/default/pg_exporter User=prometheus ExecStart=/usr/bin/pg_exporter $PG_EXPORTER_OPTS Restart=on-failure [Install] WantedBy=multi-user.targetpg_exporter-1.4.1/package/preinstall.sh000077500000000000000000000006421523237665500202020ustar00rootroot00000000000000#!/bin/bash # create a group & user named prometheus if not exists # home is set to /var/lib/prometheus (where libpq looks for ~/.pgpass), # but the directory itself is not created by this package getent group prometheus >/dev/null || groupadd -r prometheus ; /bin/true getent passwd prometheus >/dev/null || useradd -r -g prometheus -s /sbin/nologin -c "Prometheus services" -d /var/lib/prometheus prometheus exit 0 pg_exporter-1.4.1/pg_exporter.yml000066400000000000000000007132541523237665500171660ustar00rootroot00000000000000#==============================================================# # Desc : pg_exporter metrics collector definition # Ver : PostgreSQL 10 ~ 19+ and pgbouncer 1.9~1.25+ # Ctime : 2019-12-09 # Mtime : 2026-07-11 # Homepage : https://pigsty.io/docs/pg_exporter # Author : Ruohang Feng (rh@vonng.com) # License : Apache-2.0 @ https://github.com/pgsty/pg_exporter # Copyright : 2018-2026 Ruohang Feng / Vonng (rh@vonng.com) #==============================================================# #==============================================================# # 1. Config File #==============================================================# # The configuration file for pg_exporter is a YAML file. # Default configurations are retrieved via following precedence: # 1. command line args: --config= # 2. environment variables: PG_EXPORTER_CONFIG= # 3. pg_exporter.yml (Current directory) # 4. /etc/pg_exporter.yml (config file) # 5. /etc/pg_exporter (config dir) #==============================================================# # 2. Config Format #==============================================================# # pg_exporter config could be a single YAML file, or a directory containing a series of separated YAML files. # Each YAML config file consists of one or more metrics Collector definition, which are top-level objects. # If a directory is provided, all YAML in that directory will be merged in alphabetic order. # Collector definition examples are shown below. #==============================================================# # 3. Collector Example #==============================================================# # # Here is an example of a metrics collector definition # pg_primary_only: # Collector branch name. Must be UNIQUE among the entire configuration # name: pg # Collector namespace, used as METRIC PREFIX, set to branch name by default, can be override # # the same namespace may contain multiple collector branches. It`s the user`s responsibility # # to make sure that AT MOST ONE collector is picked for each namespace. # # desc: PostgreSQL basic information (on primary) # Collector description # query: | # Metrics Query SQL # # SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, # pg_current_wal_lsn() - '0/0' AS lsn, # pg_current_wal_insert_lsn() - '0/0' AS insert_lsn, # pg_current_wal_lsn() - '0/0' AS write_lsn, # pg_current_wal_flush_lsn() - '0/0' AS flush_lsn, # extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, # extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, # pg_is_in_backup() AS is_in_backup, # extract(EPOCH FROM now() - pg_backup_start_time()) AS backup_time; # # # [OPTIONAL] metadata fields, control collector behavior # ttl: 10 # Cache TTL: in seconds, how long will pg_exporter cache this collector`s query result. # timeout: 0.1 # Query Timeout: in seconds, queries that exceed this limit will be canceled. # min_version: 100000 # minimal supported version, boundary IS included. In server version number format, # max_version: 130000 # maximal supported version, boundary NOT included, In server version number format # fatal: false # Collector marked `fatal` fails, the entire scrape will abort immediately and marked as failed # skip: false # Collector marked `skip` will not be installed during the planning procedure # # tags: [cluster, primary] # Collector tags, used for planning and scheduling # # # tags are list of strings, which could be: # # * `cluster` marks this query as cluster level, so it will only execute once for the same PostgreSQL Server # # * `primary` or `master` mark this query can only run on a primary instance (WILL NOT execute if pg_is_in_recovery()) # # * `standby` or `replica` mark this query can only run on a replica instance (WILL execute if pg_is_in_recovery()) # # some special tag prefix have special interpretation: # # * `dbname:` means this query will ONLY be executed on database with name `` # # * `username:` means this query will only be executed when connect with user `` # # * `extension:` means this query will only be executed when extension `` is installed # # * `schema:` means this query will only by executed when schema `` exist # # * `not:` means this query WILL NOT be executed when exporter is tagged with `` # # * `` means this query WILL be executed when exporter is tagged with `` # # ( could not be cluster,primary,standby,master,replica,etc...) # # # One or more "predicate queries" may be defined for a metric query. These # # are run before the main metric query (after any cache hit check). If all # # of them, when run sequentially, return a single row with a single column # # boolean true result, the main metric query is executed. If any of them # # return false or return zero rows, the main query is skipped. If any # # predicate query returns more than one row, a non-boolean result, or fails # # with an error, the whole query is marked failed. Predicate queries can be # # used to check for the presence of specific functions, tables, extensions, # # settings, and vendor-specific pg features before running the main query. # # predicate_queries: # - name: predicate query name # predicate_query: | # SELECT EXISTS (SELECT 1 FROM information_schema.routines WHERE routine_schema = 'pg_catalog' AND routine_name = 'pg_backup_start_time'); # # metrics: # List of returned columns, each column must have a `name` and `usage`, `rename` and `description` are optional # - timestamp: # Column name, should be exactly the same as returned column name # usage: GAUGE # Metric type, `usage` could be # * DISCARD: completely ignoring this field # * LABEL: use columnName=columnValue as a label in metric # * GAUGE: Mark column as a gauge metric, full name will be `_` # * COUNTER: Same as above, except it is a counter rather than a gauge. # * HISTOGRAM: Aggregate one observation per SQL row into a snapshot distribution. # rename: ts # [OPTIONAL] Alias, optional, the alias will be used instead of the column name # description: xxxx # [OPTIONAL] Description of the column, will be used as a metric description # default: 0 # [OPTIONAL] Default value, will be used when column is NULL # scale: 1000 # [OPTIONAL] Scale the value by this factor # - lsn: # usage: COUNTER # description: log sequence number, current write location (on primary) # - insert_lsn: # usage: COUNTER # description: primary only, location of current wal inserting # - write_lsn: # usage: COUNTER # description: primary only, location of current wal writing # - flush_lsn: # usage: COUNTER # description: primary only, location of current wal syncing # - uptime: # usage: GAUGE # description: seconds since postmaster start # - conf_reload_time: # usage: GAUGE # description: seconds since last configuration reload # - is_in_backup: # usage: GAUGE # description: 1 if backup is in progress # - backup_time: # usage: GAUGE # description: seconds since the current backup start. null if don`t have one # # .... # you can also use rename & scale to customize the metric name and value: # - checkpoint_write_time: # rename: write_time # usage: COUNTER # scale: 1e-3 # description: Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds # - xact_age_seconds: # usage: HISTOGRAM # bucket: [1, 3, 10, 30, 100, 300, 1000, 3000] # finite increasing upper bounds # description: Open transaction age snapshot in seconds # # # HISTOGRAM is rebuilt from the current SQL result whenever the query actually executes. # # It emits cumulative `_bucket{le=...}`, `_count`, and `_sum` Gauge series, # # which may increase or decrease between snapshots. Query them directly with histogram_quantile(): # # histogram_quantile(0.95, sum by (datname, le) (pg_xact_age_seconds_bucket)) # # +Inf is automatic; the first finite bucket implicitly contains every lower value. # # Signed domains should configure finite negative boundaries and zero; do not configure -Inf. # # Do NOT apply rate(), irate(), or increase() to snapshot Histogram series. # # See docs/design/histogram.md for the authoritative contract. #==============================================================# # 4. Collector Presets #==============================================================# # pg_exporter is shipped with a series of preset collectors (already numbered and ordered by filename) # # 1xx Basic metrics: basic info, metadata, settings # 2xx Replication metrics: replication, walreceiver, downstream, sync standby, slots, subscription # 3xx Persist metrics: size, wal, background writer, checkpointer, ssl, checkpoint, recovery, slru cache, shmem usage # 4xx Activity metrics: backend count group by state, wait event, locks, xacts, queries # 5xx Progress metrics: clustering, vacuuming, indexing, basebackup, copy # 6xx Database metrics: pg_database, publication, subscription # 7xx Object metrics: pg_class, table, index, function, sequence, default partition # 8xx Optional metrics: optional metrics collector (disable by default, slow queries) # 9xx Pgbouncer metrics: metrics from pgbouncer admin database `pgbouncer` # # 100-599 Metrics for entire database cluster (scrape once) # 600-899 Metrics for single database instance (scrape for each database ,except for pg_db itself) #==============================================================# # 5. Cache TTL #==============================================================# # Cache can be used for reducing query overhead, it can be enabled by setting a non-zero value for `ttl` # It is highly recommended to use cache to avoid duplicate scrapes. Especially when you got multiple Prometheus # scraping the same instance with slow monitoring queries. Setting `ttl` to zero or leaving blank will disable # result caching, which is the default behavior # # TTL has to be smaller than your scrape interval. 15s scrape interval and 10s TTL is a good start for # production environment. Some expensive monitoring queries (such as size/bloat check) will have longer `ttl` # which can also be used as a mechanism to achieve `different scrape frequency` #==============================================================# # 6. Query Timeout #==============================================================# # Collectors can be configured with an optional Timeout. If the collector's query executes more than that # timeout, it will be canceled immediately. Setting the `timeout` to 0 or leaving blank will reset it to # default timeout 0.1 (100ms). Setting it to any negative number will disable the query timeout feature. # All queries have a default timeout of 100ms, if exceeded, the query will be canceled immediately to avoid # avalanche. You can explicitly overwrite that option. but beware: in some extreme cases, if all your # timeouts sum up greater your scrape/cache interval (usually 15s), the queries may still be jammed. # or, you can just disable potential slow queries. #==============================================================# # 7. Version Compatibility #==============================================================# # Each collector has two optional version compatibility parameters: `min_version` and `max_version`. # These two parameters specify the version compatibility of the collector. If target postgres/pgbouncer's # version is less than `min_version`, or higher than `max_version`, the collector will not be installed. # These two parameters are using PostgreSQL server version number format, which is a 6-digit integer # format as :. # For example, 090600 stands for 9.6, and 120100 stands for 12.1 # And beware that version compatibility range is left-inclusive right exclusive: [min, max), set to zero or # leaving blank will affect as -inf or +inf #==============================================================# # 8. Fatality #==============================================================# # If a collector is marked with `fatal` falls, the entire scrape operation will be marked as fail and key metrics # `pg_up` / `pgbouncer_up` will be reset to 0. It is always a good practice to set up AT LEAST ONE fatal # collector for pg_exporter. `pg.pg_primary_only` and `pgbouncer_list` are the default fatal collector. # # If a collector without `fatal` flag fails, it will increase global fail counters. But the scrape operation # will carry on. The entire scrape result will not be marked as faile, thus will not affect the `_up` metric. #==============================================================# # 9. Skip #==============================================================# # Collector with `skip` flag set to true will NOT be installed. # This could be a handy option to disable collectors #==============================================================# # 10. Tags and Planning #==============================================================# # Tags are designed for collector planning & schedule. It can be handy to customize which queries run # on which instances. And thus you can use one-single monolith config for multiple environments # # Tags are a list of strings, each string could be: # Pre-defined special tags # * `cluster` marks this collector as cluster level, so it will ONLY BE EXECUTED ONCE for the same PostgreSQL Server # * `primary` or `master` mark this collector as primary-only, so it WILL NOT work iff pg_is_in_recovery() # * `standby` or `replica` mark this collector as replica-only, so it WILL work iff pg_is_in_recovery() # Special tag prefix which have different interpretation: # * `dbname:` means this collector will ONLY work on database with name `` # * `username:` means this collector will ONLY work when connect with user `` # * `extension:` means this collector will ONLY work when extension `` is installed # * `schema:` means this collector will only work when schema `` exists # Customized positive tags (filter) and negative tags (taint) # * `not:` means this collector WILL NOT work when exporter is tagged with `` # * `` means this query WILL work if exporter is tagged with `` (special tags not included) # # pg_exporter will trigger the Planning procedure after connecting to the target. It will gather database facts # and match them with tags and other metadata (such as supported version range). Collector will only # be installed if and only if it is compatible with the target server. #==============================================================# # 0110 pg #==============================================================# pg_primary_only: name: pg desc: PostgreSQL basic information (on primary) query: |- SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, extract(EPOCH FROM pg_postmaster_start_time()) AS boot_time, pg_current_wal_lsn() - '0/0' AS lsn, pg_current_wal_insert_lsn() - '0/0' AS insert_lsn, pg_current_wal_lsn() - '0/0' AS write_lsn, pg_current_wal_flush_lsn() - '0/0' AS flush_lsn, NULL::BIGINT AS receive_lsn, NULL::BIGINT AS replay_lsn, extract(EPOCH FROM pg_conf_load_time()) AS reload_time, extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, NULL::FLOAT AS last_replay_time, 0::FLOAT AS lag, pg_is_in_recovery() AS is_in_recovery, FALSE AS is_wal_replay_paused; tags: [ cluster, primary ] ttl: 1 min_version: 100000 fatal: true skip: false metrics: - timestamp: { usage: GAUGE ,description: "current database timestamp in unix epoch" } - uptime: { usage: GAUGE ,description: "seconds since postmaster start" } - boot_time: { usage: GAUGE ,description: "postmaster boot timestamp in unix epoch" } - lsn: { usage: COUNTER ,description: "log sequence number, current write location" } - insert_lsn: { usage: COUNTER ,description: "primary only, location of current wal inserting" } - write_lsn: { usage: COUNTER ,description: "primary only, location of current wal writing" } - flush_lsn: { usage: COUNTER ,description: "primary only, location of current wal syncing" } - receive_lsn: { usage: COUNTER ,description: "replica only, location of wal synced to disk" } - replay_lsn: { usage: COUNTER ,description: "replica only, location of wal applied" } - reload_time: { usage: GAUGE ,description: "time when configuration was last reloaded" } - conf_reload_time: { usage: GAUGE ,description: "seconds since last configuration reload" } - last_replay_time: { usage: GAUGE ,description: "time when last transaction been replayed" } - lag: { usage: GAUGE ,description: "replica only, replication lag in seconds" } - is_in_recovery: { usage: GAUGE ,description: "1 if in recovery mode" } - is_wal_replay_paused: { usage: GAUGE ,description: "1 if wal play is paused" } pg_replica_only: name: pg desc: PostgreSQL basic information (on replica) query: |- SELECT extract(EPOCH FROM CURRENT_TIMESTAMP) AS timestamp, extract(EPOCH FROM now() - pg_postmaster_start_time()) AS uptime, extract(EPOCH FROM pg_postmaster_start_time()) AS boot_time, pg_last_wal_replay_lsn() - '0/0' AS lsn, NULL::BIGINT AS insert_lsn, NULL::BIGINT AS write_lsn, NULL::BIGINT AS flush_lsn, pg_last_wal_receive_lsn() - '0/0' AS receive_lsn, pg_last_wal_replay_lsn() - '0/0' AS replay_lsn, extract(EPOCH FROM pg_conf_load_time()) AS reload_time, extract(EPOCH FROM now() - pg_conf_load_time()) AS conf_reload_time, extract(EPOCH FROM pg_last_xact_replay_timestamp()) AS last_replay_time, CASE WHEN pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn() THEN 0 ELSE EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp()) END AS lag, pg_is_in_recovery() AS is_in_recovery, pg_is_wal_replay_paused() AS is_wal_replay_paused; tags: [ cluster, replica ] ttl: 1 min_version: 100000 fatal: true skip: false metrics: - timestamp: { usage: GAUGE ,description: "current database timestamp in unix epoch" } - uptime: { usage: GAUGE ,description: "seconds since postmaster start" } - boot_time: { usage: GAUGE ,description: "postmaster boot timestamp in unix epoch" } - lsn: { usage: COUNTER ,description: "log sequence number, current write location" } - insert_lsn: { usage: COUNTER ,description: "primary only, location of current wal inserting" } - write_lsn: { usage: COUNTER ,description: "primary only, location of current wal writing" } - flush_lsn: { usage: COUNTER ,description: "primary only, location of current wal syncing" } - receive_lsn: { usage: COUNTER ,description: "replica only, location of wal synced to disk" } - replay_lsn: { usage: COUNTER ,description: "replica only, location of wal applied" } - reload_time: { usage: GAUGE ,description: "time when configuration was last reloaded" } - conf_reload_time: { usage: GAUGE ,description: "seconds since last configuration reload" } - last_replay_time: { usage: GAUGE ,description: "time when last transaction been replayed" } - lag: { usage: GAUGE ,description: "replica only, replication lag in seconds" } - is_in_recovery: { usage: GAUGE ,description: "1 if in recovery mode" } - is_wal_replay_paused: { usage: GAUGE ,description: "1 if wal play is paused" } #==============================================================# # 0120 pg_meta #==============================================================# pg_meta_13: name: pg_meta desc: PostgreSQL meta info for pg 13+, with extra primary conninfo query: | SELECT (SELECT system_identifier FROM pg_control_system()) AS cluster_id, current_setting('cluster_name') AS cluster_name, current_setting('port') AS listen_port, current_setting('data_directory', true) AS data_dir, current_setting('config_file', true) AS conf_path, current_setting('hba_file', true) AS hba_path, current_setting('wal_level') AS wal_level, current_setting('server_encoding') AS encoding, current_setting('server_version') AS version, current_setting('server_version_num') AS ver_num, version() AS ver_str, current_setting('shared_preload_libraries', true) AS extensions, current_setting('primary_conninfo', true) AS primary_conninfo, 1 AS info ttl: 10 min_version: 130000 tags: [ cluster ] metrics: - cluster_id: { usage: LABEL ,description: "cluster system identifier" } - cluster_name: { usage: LABEL ,description: "cluster name" } - listen_port: { usage: LABEL ,description: "listen port" } - data_dir: { usage: LABEL ,description: "path to data directory" } - conf_path: { usage: LABEL ,description: "path to postgresql.conf" } - hba_path: { usage: LABEL ,description: "path to pg_hba.conf" } - wal_level: { usage: LABEL ,description: "wal level" } - encoding: { usage: LABEL ,description: "server encoding" } - version: { usage: LABEL ,description: "server version in human-readable format" } - ver_num: { usage: LABEL ,description: "server version number in machine-readable format" } - ver_str: { usage: LABEL ,description: "complete version string" } - extensions: { usage: LABEL ,description: "server installed preload libraries" } - primary_conninfo: { usage: LABEL ,description: "connection string to upstream (do not set password here)" } - info: { usage: GAUGE ,description: "constant 1" } pg_meta_10: name: pg_meta desc: PostgreSQL meta info query: | SELECT (SELECT system_identifier FROM pg_control_system()) AS cluster_id, current_setting('cluster_name') AS cluster_name, current_setting('port') AS listen_port, current_setting('data_directory', true) AS data_dir, current_setting('config_file', true) AS conf_path, current_setting('hba_file', true) AS hba_path, current_setting('wal_level') AS wal_level, current_setting('server_encoding') AS encoding, current_setting('server_version') AS version, current_setting('server_version_num') AS ver_num, version() AS ver_str, current_setting('shared_preload_libraries', true) AS extensions, 'N/A' AS primary_conninfo, 1 AS info ttl: 10 min_version: 090600 max_version: 130000 tags: [ cluster ] metrics: - cluster_id: { usage: LABEL ,description: "cluster system identifier" } - cluster_name: { usage: LABEL ,description: "cluster name" } - listen_port: { usage: LABEL ,description: "listen port" } - data_dir: { usage: LABEL ,description: "path to data directory" } - conf_path: { usage: LABEL ,description: "path to postgresql.conf" } - hba_path: { usage: LABEL ,description: "path to pg_hba.conf" } - wal_level: { usage: LABEL ,description: "wal level" } - encoding: { usage: LABEL ,description: "server encoding" } - version: { usage: LABEL ,description: "server version in human-readable format" } - ver_num: { usage: LABEL ,description: "server version number in machine-readable format" } - ver_str: { usage: LABEL ,description: "complete version string" } - extensions: { usage: LABEL ,description: "server installed preload libraries" } - primary_conninfo: { usage: LABEL ,description: "connection string to upstream (do not set password here)" } - info: { usage: GAUGE ,description: "constant 1" } #==============================================================# # 0130 pg_setting #==============================================================# # Key PostgreSQL configuration parameters # All parameters use current_setting(name, missing_ok) for version safety # Parameters introduced after PG10 use missing_ok=true to return NULL on older versions pg_setting: name: pg_setting desc: PostgreSQL shared configuration parameters (shared across all databases) query: | SELECT current_setting('max_connections')::int AS max_connections, current_setting('max_prepared_transactions')::int AS max_prepared_transactions, current_setting('max_locks_per_transaction')::int AS max_locks_per_transaction, current_setting('max_worker_processes')::int AS max_worker_processes, current_setting('max_parallel_workers')::int AS max_parallel_workers, current_setting('max_parallel_workers_per_gather')::int AS max_parallel_workers_per_gather, current_setting('max_parallel_maintenance_workers', true)::int AS max_parallel_maintenance_workers, current_setting('max_replication_slots')::int AS max_replication_slots, current_setting('max_wal_senders')::int AS max_wal_senders, current_setting('block_size')::int AS block_size, current_setting('wal_block_size')::int AS wal_block_size, pg_size_bytes(current_setting('segment_size')) AS segment_size, pg_size_bytes(current_setting('wal_segment_size')) AS wal_segment_size, CASE current_setting('data_checksums') WHEN 'on' THEN 1 ELSE 0 END AS data_checksums, CASE current_setting('wal_log_hints') WHEN 'on' THEN 1 ELSE 0 END AS wal_log_hints, CASE current_setting('fsync') WHEN 'on' THEN 1 ELSE 0 END AS fsync, CASE current_setting('full_page_writes') WHEN 'on' THEN 1 ELSE 0 END AS full_page_writes, CASE current_setting('wal_level') WHEN 'logical' THEN 3 WHEN 'replica' THEN 2 WHEN 'minimal' THEN 1 ELSE 0 END AS wal_level, pg_size_bytes(current_setting('min_wal_size')) AS min_wal_size, pg_size_bytes(current_setting('max_wal_size')) AS max_wal_size, pg_size_bytes(current_setting('max_slot_wal_keep_size', true)) AS max_slot_wal_keep_size, pg_size_bytes(current_setting('shared_buffers')) AS shared_buffers, pg_size_bytes(current_setting('work_mem')) AS work_mem, pg_size_bytes(current_setting('maintenance_work_mem')) AS maintenance_work_mem, pg_size_bytes(current_setting('effective_cache_size')) AS effective_cache_size, pg_size_bytes(current_setting('shared_memory_size', true)) AS shared_memory_size, CASE current_setting('huge_pages_status', true) WHEN 'on' THEN 1 WHEN 'off' THEN 0 WHEN 'unknown' THEN -1 ELSE NULL END AS hugepage_status, current_setting('shared_memory_size_in_huge_pages', true)::int AS hugepage_count, CASE current_setting('archive_mode') WHEN 'off' THEN 0 WHEN 'on' THEN 1 WHEN 'always' THEN 2 ELSE -1 END AS archive_mode, CASE current_setting('autovacuum') WHEN 'on' THEN 1 ELSE 0 END AS autovacuum, current_setting('autovacuum_max_workers')::int AS autovacuum_max_workers, extract(epoch from current_setting('checkpoint_timeout')::interval)::int AS checkpoint_timeout, current_setting('checkpoint_completion_target')::float AS checkpoint_completion_target, CASE current_setting('hot_standby') WHEN 'on' THEN 1 ELSE 0 END AS hot_standby, CASE current_setting('synchronous_commit') WHEN 'off' THEN 0 WHEN 'local' THEN 1 WHEN 'remote_write' THEN 2 WHEN 'on' THEN 3 WHEN 'remote_apply' THEN 4 ELSE -1 END AS synchronous_commit, CASE current_setting('io_method', true) WHEN 'sync' THEN 0 WHEN 'worker' THEN 1 WHEN 'io_uring' THEN 2 ELSE NULL END AS io_method; ttl: 10 min_version: 100000 tags: [ cluster ] metrics: - max_connections: { usage: GAUGE ,description: "maximum number of concurrent connections to the database server" } - max_prepared_transactions: { usage: GAUGE ,description: "maximum number of transactions that can be in the prepared state simultaneously" } - max_locks_per_transaction: { usage: GAUGE ,description: "maximum number of locks per transaction" } - max_worker_processes: { usage: GAUGE ,description: "maximum number of background processes" } - max_parallel_workers: { usage: GAUGE ,description: "maximum number of parallel workers that can be active at one time" } - max_parallel_workers_per_gather: { usage: GAUGE ,description: "maximum number of parallel workers per Gather node" } - max_parallel_maintenance_workers: { usage: GAUGE ,description: "maximum number of parallel maintenance workers (PG11+, NULL on older)" } - max_replication_slots: { usage: GAUGE ,description: "maximum number of replication slots" } - max_wal_senders: { usage: GAUGE ,description: "maximum number of concurrent WAL sender connections" } - block_size: { usage: GAUGE ,description: "database block size in bytes (default 8192)" } - wal_block_size: { usage: GAUGE ,description: "WAL block size in bytes" } - segment_size: { usage: GAUGE ,description: "database file segment size in bytes" } - wal_segment_size: { usage: GAUGE ,description: "WAL segment size in bytes" } - data_checksums: { usage: GAUGE ,description: "data checksums enabled, 1=on 0=off" } - wal_log_hints: { usage: GAUGE ,description: "WAL log hints enabled, 1=on 0=off" } - fsync: { usage: GAUGE ,description: "fsync enabled (CRITICAL for data safety), 1=on 0=off" } - full_page_writes: { usage: GAUGE ,description: "full page writes enabled, 1=on 0=off" } - wal_level: { usage: GAUGE ,description: "WAL level, 1=minimal 2=replica 3=logical" } - min_wal_size: { usage: GAUGE ,description: "minimum WAL size in bytes" } - max_wal_size: { usage: GAUGE ,description: "maximum WAL size in bytes" } - max_slot_wal_keep_size: { usage: GAUGE ,description: "maximum WAL size retained by replication slots in bytes (PG13+, NULL on older)" } - shared_buffers: { usage: GAUGE ,description: "shared buffer size in bytes" } - work_mem: { usage: GAUGE ,description: "work memory size in bytes" } - maintenance_work_mem: { usage: GAUGE ,description: "maintenance work memory size in bytes" } - effective_cache_size: { usage: GAUGE ,description: "planner's assumption about effective OS cache size in bytes" } - shared_memory_size: { usage: GAUGE ,description: "total shared memory size in bytes (PG13+, NULL on older)" } - hugepage_status: { usage: GAUGE ,description: "huge pages status, 1=on 0=off -1=unknown NULL=unavailable (PG14+)" } - hugepage_count: { usage: GAUGE ,description: "number of huge pages needed for shared memory (PG14+, NULL on older)" } - archive_mode: { usage: GAUGE ,description: "archive mode, 0=off 1=on 2=always" } - autovacuum: { usage: GAUGE ,description: "autovacuum enabled, 1=on 0=off" } - autovacuum_max_workers: { usage: GAUGE ,description: "maximum number of autovacuum worker processes" } - checkpoint_timeout: { usage: GAUGE ,description: "checkpoint timeout in seconds" } - checkpoint_completion_target: { usage: GAUGE ,description: "checkpoint completion target (0.0-1.0)" } - hot_standby: { usage: GAUGE ,description: "hot standby mode enabled, 1=on 0=off" } - synchronous_commit: { usage: GAUGE ,description: "synchronous commit level, 0=off 1=local 2=remote_write 3=on 4=remote_apply" } - io_method: { usage: GAUGE ,description: "I/O method (PG18+), 0=sync 1=worker 2=io_uring NULL=unavailable" } #==============================================================# # 0210 pg_repl #==============================================================# pg_repl_12: name: pg_repl desc: PostgreSQL replication stat metrics 12+ query: | SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, pid::TEXT, client_port, CASE state WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, CASE sync_state WHEN 'async' THEN 0 WHEN 'potential' THEN 1 WHEN 'sync' THEN 2 WHEN 'quorum' THEN 3 ELSE -1 END AS sync_state, sync_priority, backend_xmin::TEXT::BIGINT AS backend_xmin, current.lsn - '0/0' AS lsn, current.lsn - sent_lsn AS sent_diff, current.lsn - write_lsn AS write_diff, current.lsn - flush_lsn AS flush_diff, current.lsn - replay_lsn AS replay_diff, sent_lsn - '0/0' AS sent_lsn, write_lsn - '0/0' AS write_lsn, flush_lsn - '0/0' AS flush_lsn, replay_lsn - '0/0' AS replay_lsn, coalesce(extract(EPOCH FROM write_lag), 0) AS write_lag, coalesce(extract(EPOCH FROM flush_lag), 0) AS flush_lag, coalesce(extract(EPOCH FROM replay_lag), 0) AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time, extract(EPOCH FROM reply_time) AS reply_time FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END AS lsn) current; ttl: 10 min_version: 120000 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback." } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } - reply_time: { usage: GAUGE ,description: "Send time of last reply message received from standby server" } pg_repl_10: name: pg_repl desc: PostgreSQL replication stat metrics v10 v11 query: | SELECT application_name AS appname, usename, coalesce(client_addr::TEXT,'localhost') AS address, pid::TEXT, client_port, CASE state WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, CASE sync_state WHEN 'async' THEN 0 WHEN 'potential' THEN 1 WHEN 'sync' THEN 2 WHEN 'quorum' THEN 3 ELSE -1 END AS sync_state, sync_priority, backend_xmin::TEXT::BIGINT AS backend_xmin, current.lsn - '0/0' AS lsn, current.lsn - sent_lsn AS sent_diff, current.lsn - write_lsn AS write_diff, current.lsn - flush_lsn AS flush_diff, current.lsn - replay_lsn AS replay_diff, sent_lsn - '0/0' AS sent_lsn, write_lsn - '0/0' AS write_lsn, flush_lsn - '0/0' AS flush_lsn, replay_lsn - '0/0' AS replay_lsn, coalesce(extract(EPOCH FROM write_lag), 0) AS write_lag, coalesce(extract(EPOCH FROM flush_lag), 0) AS flush_lag, coalesce(extract(EPOCH FROM replay_lag), 0) AS replay_lag, extract(EPOCH FROM current_timestamp) AS "time", extract(EPOCH FROM backend_start) AS launch_time FROM pg_stat_replication, (SELECT CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END AS lsn) current; ttl: 10 min_version: 100000 max_version: 120000 tags: [ cluster ] metrics: - appname: { usage: LABEL ,description: "Name of the application that is connected to this WAL sender" } - usename: { usage: LABEL ,description: "Name of the user logged into this WAL sender process" } - address: { usage: LABEL ,description: "IP address of the client connected to this WAL sender, localhost for unix socket" } - pid: { usage: LABEL ,description: "Process ID of the WAL sender process" } - client_port: { usage: GAUGE ,description: "TCP port number that the client is using for communication with this WAL sender, or -1 if a Unix socket is used" } - state: { usage: GAUGE ,description: "Current WAL sender encoded state 0-4 for streaming|startup|catchup|backup|stopping" } - sync_state: { usage: GAUGE ,description: "Encoded synchronous state of this standby server, 0-3 for async|potential|sync|quorum" } - sync_priority: { usage: GAUGE ,description: "Priority of this standby server for being chosen as the synchronous standby" } - backend_xmin: { usage: COUNTER ,description: "This standby's xmin horizon reported by hot_standby_feedback." } - lsn: { usage: COUNTER ,description: "Current log position on this server" } - sent_diff: { usage: GAUGE ,description: "Last log position sent to this standby server diff with current lsn" } - write_diff: { usage: GAUGE ,description: "Last log position written to disk by this standby server diff with current lsn" } - flush_diff: { usage: GAUGE ,description: "Last log position flushed to disk by this standby server diff with current lsn" } - replay_diff: { usage: GAUGE ,description: "Last log position replayed into the database on this standby server diff with current lsn" } - sent_lsn: { usage: COUNTER ,description: "Last write-ahead log location sent on this connection" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location written to disk by this standby server" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location flushed to disk by this standby server" } - replay_lsn: { usage: COUNTER ,description: "Last write-ahead log location replayed into the database on this standby server" } - write_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it" } - flush_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it" } - replay_lag: { usage: GAUGE ,description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it" } - time: { usage: COUNTER ,description: "Current timestamp in unix epoch" } - launch_time: { usage: COUNTER ,description: "Time when this process was started, i.e., when the client connected to this WAL sender" } #==============================================================# # 0220 pg_sync_standby #==============================================================# pg_sync_standby: name: pg_sync_standby desc: PostgreSQL synchronous standby status and names query: | SELECT CASE WHEN names <> '' THEN names ELSE '' END AS names, CASE WHEN names <> '' THEN 1 ELSE 0 END AS enabled FROM (SELECT current_setting('synchronous_standby_names') AS names) n; ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - names: { usage: LABEL ,description: "List of standby servers that can support synchronous replication, if not enabled" } - enabled: { usage: GAUGE ,description: "Synchronous commit enabled, 1 if enabled, 0 if disabled" } #==============================================================# # 0230 pg_downstream #==============================================================# pg_downstream: name: pg_downstream desc: PostgreSQL replication client count group by state query: | SELECT l.state, coalesce(count, 0 ) AS count FROM unnest(ARRAY ['streaming','startup','catchup', 'backup', 'stopping']) l(state) LEFT JOIN (SELECT state, count(*) AS count FROM pg_stat_replication GROUP BY state)r ON l.state = r.state; ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - state: { usage: LABEL ,description: "Replication client state, could be one of startup|catchup|streaming|backup|stopping" } - count: { usage: GAUGE ,description: "Count of corresponding state" } #==============================================================# # 0240 pg_slot #==============================================================# pg_slot_17: name: pg_slot desc: PostgreSQL replication slot metrics v17, slot also exists on standby query: |- SELECT s.slot_name, s.slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,two_phase,conflicting,failover,synced, xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes, safe_wal_size, CASE wal_status WHEN 'reserved' THEN 0 WHEN 'extended' THEN 1 WHEN 'unreserved' THEN 2 WHEN 'lost' THEN 3 ELSE -1 END AS wal_status, spill_txns,spill_count,spill_bytes,stream_txns,stream_count,stream_bytes,total_txns,total_bytes,extract(EPOCH FROM stats_reset) AS reset_time, extract(EPOCH FROM inactive_since) AS inactive_since, CASE invalidation_reason WHEN 'wal_removed' THEN 1 WHEN 'rows_removed' THEN 2 WHEN 'wal_level_insufficient' THEN 3 WHEN 'idle_timeout' THEN 4 ELSE 0 END AS invalidation_reason FROM pg_replication_slots s LEFT OUTER JOIN pg_stat_replication_slots ss ON s.slot_name = ss.slot_name; ttl: 10 min_version: 170000 tags: [ cluster ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - two_phase: { usage: GAUGE ,description: "True(1) if the slot is enabled for decoding prepared transactions. Always false for physical slots." } - conflicting: { usage: GAUGE ,description: "True(1) if this logical slot conflicted with recovery. Always NULL for physical slots." } - failover: { usage: GAUGE ,description: "True(1) if this is a logical slot enabled to be synced to the standbys" } - synced: { usage: GAUGE ,description: "True(1) if this is a logical slot that was synced from a primary server" } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } - safe_wal_size: { usage: GAUGE ,description: "bytes that can be written to WAL which will not make slot into lost" } - wal_status: { usage: GAUGE ,description: "WAL reserve status 0-3 means reserved,extended,unreserved,lost, -1 means other" } - spill_txns: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding (subtrans included)" } - spill_count: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding" } - spill_bytes: { usage: COUNTER ,description: "Bytes that spilled to disk due to logical decode mem exceeding" } - stream_txns: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_count: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_bytes: { usage: COUNTER ,description: "Bytes that streamed to decoding output plugin after mem exceed" } - total_txns: { usage: COUNTER ,description: "Number of decoded xacts sent to the decoding output plugin for this slot" } - total_bytes: { usage: COUNTER ,description: "Number of decoded bytes sent to the decoding output plugin for this slot" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } - invalidation_reason: { usage: GAUGE ,description: "ok=0, wal_removed=1, rows_removed=2, wal_level_insufficient=3, idle_timeout=4" } - inactive_since: { usage: GAUGE ,description: "The time when the slot became inactive" } pg_slot_16: name: pg_slot desc: PostgreSQL replication slot metrics v16 with conflicting, now slot also exists on standby query: |- SELECT s.slot_name, s.slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,two_phase,conflicting,xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes, safe_wal_size, CASE wal_status WHEN 'reserved' THEN 0 WHEN 'extended' THEN 1 WHEN 'unreserved' THEN 2 WHEN 'lost' THEN 3 ELSE -1 END AS wal_status, spill_txns,spill_count,spill_bytes,stream_txns,stream_count,stream_bytes,total_txns,total_bytes,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_replication_slots s LEFT OUTER JOIN pg_stat_replication_slots ss ON s.slot_name = ss.slot_name; ttl: 10 min_version: 160000 max_version: 170000 tags: [ cluster ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - two_phase: { usage: GAUGE ,description: "True(1) if the slot is enabled for decoding prepared transactions. Always false for physical slots." } - conflicting: { usage: GAUGE ,description: "True if this logical slot conflicted with recovery. Always NULL for physical slots." } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } - safe_wal_size: { usage: GAUGE ,description: "bytes that can be written to WAL which will not make slot into lost" } - wal_status: { usage: GAUGE ,description: "WAL reserve status 0-3 means reserved,extended,unreserved,lost, -1 means other" } - spill_txns: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding (subtrans included)" } - spill_count: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding" } - spill_bytes: { usage: COUNTER ,description: "Bytes that spilled to disk due to logical decode mem exceeding" } - stream_txns: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_count: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_bytes: { usage: COUNTER ,description: "Bytes that streamed to decoding output plugin after mem exceed" } - total_txns: { usage: COUNTER ,description: "Number of decoded xacts sent to the decoding output plugin for this slot" } - total_bytes: { usage: COUNTER ,description: "Number of decoded bytes sent to the decoding output plugin for this slot" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } pg_slot_14: name: pg_slot desc: PostgreSQL replication slot metrics v14 with pg_stat_replication_slots metrics query: |- SELECT s.slot_name, s.slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,two_phase,xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes, safe_wal_size, CASE wal_status WHEN 'reserved' THEN 0 WHEN 'extended' THEN 1 WHEN 'unreserved' THEN 2 WHEN 'lost' THEN 3 ELSE -1 END AS wal_status, spill_txns,spill_count,spill_bytes,stream_txns,stream_count,stream_bytes,total_txns,total_bytes,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_replication_slots s LEFT OUTER JOIN pg_stat_replication_slots ss ON s.slot_name = ss.slot_name; ttl: 10 min_version: 140000 max_version: 160000 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - two_phase: { usage: GAUGE ,description: "True(1) if the slot is enabled for decoding prepared transactions. Always false for physical slots." } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } - safe_wal_size: { usage: GAUGE ,description: "bytes that can be written to WAL which will not make slot into lost" } - wal_status: { usage: GAUGE ,description: "WAL reserve status 0-3 means reserved,extended,unreserved,lost, -1 means other" } - spill_txns: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding (subtrans included)" } - spill_count: { usage: COUNTER ,description: "Xacts that spilled to disk due to logical decode mem exceeding" } - spill_bytes: { usage: COUNTER ,description: "Bytes that spilled to disk due to logical decode mem exceeding" } - stream_txns: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_count: { usage: COUNTER ,description: "Xacts that streamed to decoding output plugin after mem exceed" } - stream_bytes: { usage: COUNTER ,description: "Bytes that streamed to decoding output plugin after mem exceed" } - total_txns: { usage: COUNTER ,description: "Number of decoded xacts sent to the decoding output plugin for this slot" } - total_bytes: { usage: COUNTER ,description: "Number of decoded bytes sent to the decoding output plugin for this slot" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } pg_slot_13: name: pg_slot desc: PostgreSQL replication slot metrics v13 (wal safe size and status) query: |- SELECT slot_name, slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes, safe_wal_size, CASE wal_status WHEN 'reserved' THEN 0 WHEN 'extended' THEN 1 WHEN 'unreserved' THEN 2 WHEN 'lost' THEN 3 ELSE -1 END AS wal_status FROM pg_replication_slots; ttl: 10 min_version: 130000 max_version: 140000 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } - safe_wal_size: { usage: GAUGE ,description: "bytes that can be written to WAL which will not make slot into lost" } - wal_status: { usage: GAUGE ,description: "WAL reserve status 0-3 means reserved,extended,unreserved,lost, -1 means other" } pg_slot_10: name: pg_slot desc: PostgreSQL replication slot metrics 10 ~ 12 query: |- SELECT slot_name, slot_type, plugin, database AS datname,datoid,active_pid, active,temporary,xmin::TEXT::BIGINT AS xmin,catalog_xmin::TEXT::BIGINT AS catalog_xmin, restart_lsn - '0/0' AS restart_lsn, confirmed_flush_lsn - '0/0' AS confirm_lsn, CASE WHEN pg_is_in_recovery() THEN pg_last_wal_replay_lsn() ELSE pg_current_wal_lsn() END - restart_lsn AS retained_bytes FROM pg_replication_slots; ttl: 10 min_version: 100000 max_version: 130000 tags: [ cluster, primary ] metrics: - slot_name: { usage: LABEL ,description: "A unique, cluster-wide identifier for the replication slot" } - slot_type: { usage: LABEL ,description: "The slot type, physical or logical" } - plugin: { usage: LABEL ,description: "The base name of the shared object containing the output plugin this logical slot is using, or null for physical slots." } - datname: { usage: LABEL ,description: "The name of the database this slot is associated with, logical slots only, null for physical slot" } - datoid: { usage: GAUGE ,description: "The OID of the database this slot is associated with, logical slots only, null for physical slot" } - active_pid: { usage: GAUGE ,description: "The process ID of the session streaming data for this slot. NULL if inactive." } - active: { usage: GAUGE ,description: "True(1) if this slot is currently actively being used" } - temporary: { usage: GAUGE ,description: "True(1) if this is a temporary replication slot." } - xmin: { usage: COUNTER ,description: "The oldest transaction that this slot needs the database to retain." } - catalog_xmin: { usage: COUNTER ,description: "The oldest transaction affecting the system catalogs that this slot needs the database to retain." } - restart_lsn: { usage: COUNTER ,description: "The address (LSN) of oldest WAL which still might be required by the consumer of this slot" } - confirm_lsn: { usage: COUNTER ,description: "The address (LSN) up to which the logical slot's consumer has confirmed receiving data." } - retained_bytes: { usage: GAUGE ,description: "Size of bytes that retained for this slot" } #==============================================================# # 0250 pg_recv #==============================================================# pg_recv_13: name: pg_recv desc: PostgreSQL walreceiver metrics 13+ query: |- SELECT coalesce(sender_host, (regexp_match(conninfo, '.*host=(\S+).*'))[1]) AS sender_host, coalesce(sender_port::TEXT, (regexp_match(conninfo, '.*port=(\S+).*'))[1]) AS sender_port, coalesce(slot_name, 'NULL') AS slot_name, pid, CASE status WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 WHEN 'connecting' THEN 5 ELSE -1 END AS state, receive_start_lsn - '0/0' AS init_lsn,receive_start_tli AS init_tli, flushed_lsn - '0/0' AS flush_lsn,written_lsn - '0/0' AS write_lsn, received_tli AS flush_tli, latest_end_lsn - '0/0' AS reported_lsn, last_msg_send_time AS msg_send_time,last_msg_receipt_time AS msg_recv_time,latest_end_time AS reported_time,now() AS time FROM pg_stat_wal_receiver; ttl: 10 min_version: 130000 tags: [ cluster, replica ] metrics: - sender_host: { usage: LABEL ,description: "Host of the PostgreSQL instance this WAL receiver is connected to" } - sender_port: { usage: LABEL ,description: "Port number of the PostgreSQL instance this WAL receiver is connected to." } - slot_name: { usage: LABEL ,description: "Replication slot name used by this WAL receiver" } - pid: { usage: GAUGE ,description: "Process ID of the WAL receiver process" } - state: { usage: GAUGE ,description: "Encoded activity status of the WAL receiver process 0-5 for streaming|startup|catchup|backup|stopping|connecting" } - init_lsn: { usage: COUNTER ,description: "First write-ahead log location used when WAL receiver is started" } - init_tli: { usage: COUNTER ,description: "First timeline number used when WAL receiver is started" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and flushed to disk" } - write_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and written to disk, but not flushed." } - flush_tli: { usage: COUNTER ,description: "Timeline number of last write-ahead log location received and flushed to disk" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - time: { usage: GAUGE ,description: "Time of current snapshot" } pg_recv_11: name: pg_recv desc: PostgreSQL walreceiver metrics (11-12) query: |- SELECT coalesce(sender_host, (regexp_match(conninfo, '.*host=(\S+).*'))[1]) AS sender_host, coalesce(sender_port::TEXT, (regexp_match(conninfo, '.*port=(\S+).*'))[1]) AS sender_port, coalesce(slot_name, 'NULL') AS slot_name, pid, CASE status WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, receive_start_lsn - '0/0' AS init_lsn,receive_start_tli AS init_tli, received_lsn - '0/0' AS flush_lsn, received_tli AS flush_tli, latest_end_lsn - '0/0' AS reported_lsn, last_msg_send_time AS msg_send_time,last_msg_receipt_time AS msg_recv_time,latest_end_time AS reported_time,now() AS time FROM pg_stat_wal_receiver; ttl: 10 tags: [ cluster, replica ] min_version: 110000 max_version: 130000 metrics: - sender_host: { usage: LABEL ,description: "Host of the PostgreSQL instance this WAL receiver is connected to" } - sender_port: { usage: LABEL ,description: "Port number of the PostgreSQL instance this WAL receiver is connected to." } - slot_name: { usage: LABEL ,description: "Replication slot name used by this WAL receiver" } - pid: { usage: GAUGE ,description: "Process ID of the WAL receiver process" } - state: { usage: GAUGE ,description: "Encoded activity status of the WAL receiver process 0-4 for streaming|startup|catchup|backup|stopping" } - init_lsn: { usage: COUNTER ,description: "First write-ahead log location used when WAL receiver is started" } - init_tli: { usage: COUNTER ,description: "First timeline number used when WAL receiver is started" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and flushed to disk" } - flush_tli: { usage: COUNTER ,description: "Timeline number of last write-ahead log location received and flushed to disk" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - time: { usage: GAUGE ,description: "Time of current snapshot" } pg_recv_10: name: pg_recv desc: PostgreSQL walreceiver metrics (10) query: |- SELECT (regexp_match(conninfo, '.*host=(\S+).*'))[1] AS sender_host, (regexp_match(conninfo, '.*port=(\S+).*'))[1] AS sender_port, coalesce(slot_name, 'NULL') AS slot_name, pid, CASE status WHEN 'streaming' THEN 0 WHEN 'startup' THEN 1 WHEN 'catchup' THEN 2 WHEN 'backup' THEN 3 WHEN 'stopping' THEN 4 ELSE -1 END AS state, receive_start_lsn - '0/0' AS init_lsn,receive_start_tli AS init_tli, received_lsn - '0/0' AS flush_lsn, received_tli AS flush_tli, latest_end_lsn - '0/0' AS reported_lsn, last_msg_send_time AS msg_send_time,last_msg_receipt_time AS msg_recv_time,latest_end_time AS reported_time,now() AS time FROM pg_stat_wal_receiver; ttl: 10 tags: [ cluster, replica ] min_version: 100000 max_version: 110000 metrics: - sender_host: { usage: LABEL ,description: "Host of the PostgreSQL instance this WAL receiver is connected to" } - sender_port: { usage: LABEL ,description: "Port number of the PostgreSQL instance this WAL receiver is connected to." } - slot_name: { usage: LABEL ,description: "Replication slot name used by this WAL receiver" } - pid: { usage: GAUGE ,description: "Process ID of the WAL receiver process" } - state: { usage: GAUGE ,description: "Encoded activity status of the WAL receiver process 0-4 for streaming|startup|catchup|backup|stopping" } - init_lsn: { usage: COUNTER ,description: "First write-ahead log location used when WAL receiver is started" } - init_tli: { usage: COUNTER ,description: "First timeline number used when WAL receiver is started" } - flush_lsn: { usage: COUNTER ,description: "Last write-ahead log location already received and flushed to disk" } - flush_tli: { usage: COUNTER ,description: "Timeline number of last write-ahead log location received and flushed to disk" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - time: { usage: GAUGE ,description: "Time of current snapshot" } #==============================================================# # 0260 pg_sub #==============================================================# pg_sub_19: name: pg_sub desc: PostgreSQL subscription statistics (19+) query: |- SELECT s2.subname, s2.subid AS id, s1.pid, s1.received_lsn, s1.reported_lsn, s1.msg_send_time, s1.msg_recv_time, s1.reported_time, (s1.pid IS NOT NULL)::int AS has_worker, coalesce(s2.apply_error_count, 0) AS apply_error_count, coalesce(s2.sync_table_error_count, 0) AS sync_table_error_count, coalesce(s2.sync_seq_error_count, 0) AS sync_seq_error_count, coalesce(s2.sync_table_error_count, 0) + coalesce(s2.sync_seq_error_count, 0) AS sync_error_count, coalesce(s2.confl_insert_exists, 0) AS confl_insert_exists, coalesce(s2.confl_update_origin_differs, 0) AS confl_update_origin_differs, coalesce(s2.confl_update_exists, 0) AS confl_update_exists, coalesce(s2.confl_update_deleted, 0) AS confl_update_deleted, coalesce(s2.confl_update_missing, 0) AS confl_update_missing, coalesce(s2.confl_delete_origin_differs, 0) AS confl_delete_origin_differs, coalesce(s2.confl_delete_missing, 0) AS confl_delete_missing, coalesce(s2.confl_multiple_unique_conflicts, 0) AS confl_multiple_unique_conflicts, extract(EPOCH FROM s2.stats_reset) AS reset_time FROM pg_stat_subscription_stats s2 LEFT OUTER JOIN (SELECT subname, subid, pid, received_lsn - '0/0' AS received_lsn, latest_end_lsn - '0/0' AS reported_lsn, extract(epoch from last_msg_send_time) AS msg_send_time, extract(epoch from last_msg_receipt_time) AS msg_recv_time, extract(epoch from latest_end_time) AS reported_time FROM pg_stat_subscription WHERE relid IS NULL AND leader_pid IS NULL AND coalesce(worker_type, 'apply') = 'apply') s1 USING(subid); ttl: 10 min_version: 190000 tags: [ cluster ] metrics: - subname: { usage: LABEL ,description: "Name of this subscription" } - id: { usage: GAUGE ,description: "OID of the subscription" } - pid: { usage: GAUGE ,description: "Process ID of the subscription leader apply worker" } - has_worker: { usage: GAUGE ,description: "1 if a leader apply worker row exists in pg_stat_subscription, otherwise 0" } - received_lsn: { usage: COUNTER ,description: "Last write-ahead log location received" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - apply_error_count: { usage: COUNTER ,description: "Number of times an error occurred while applying changes" } - sync_table_error_count: { usage: COUNTER ,description: "Number of times an error occurred during the initial table synchronization" } - sync_seq_error_count: { usage: COUNTER ,description: "Number of times an error occurred during sequence synchronization" } - sync_error_count: { usage: COUNTER ,description: "Sum of sync_table_error_count and sync_seq_error_count. Drop-in replacement for the PG16-18 sync_error_count metric; on PG19+ it also includes sequence-sync errors" } - confl_insert_exists: { usage: COUNTER ,description: "Number of insert_exists conflicts while applying changes" } - confl_update_origin_differs: { usage: COUNTER ,description: "Number of update_origin_differs conflicts while applying changes" } - confl_update_exists: { usage: COUNTER ,description: "Number of update_exists conflicts while applying changes" } - confl_update_deleted: { usage: COUNTER ,description: "Number of update_deleted conflicts while applying changes" } - confl_update_missing: { usage: COUNTER ,description: "Number of update_missing conflicts while applying changes" } - confl_delete_origin_differs: { usage: COUNTER ,description: "Number of delete_origin_differs conflicts while applying changes" } - confl_delete_missing: { usage: COUNTER ,description: "Number of delete_missing conflicts while applying changes" } - confl_multiple_unique_conflicts: { usage: COUNTER ,description: "Number of multiple_unique_conflicts conflicts while applying changes" } - reset_time: { usage: GAUGE ,description: "Time at which these subscription statistics were last reset" } pg_sub_16: name: pg_sub desc: PostgreSQL subscription statistics (16-18) query: |- SELECT s1.subname, subid AS id, pid, received_lsn, reported_lsn, msg_send_time, msg_recv_time, reported_time, apply_error_count, sync_error_count FROM (SELECT subname, subid, pid, received_lsn - '0/0' AS received_lsn, latest_end_lsn - '0/0' AS reported_lsn, extract(epoch from last_msg_send_time) AS msg_send_time, extract(epoch from last_msg_receipt_time) AS msg_recv_time, extract(epoch from latest_end_time) AS reported_time FROM pg_stat_subscription WHERE relid IS NULL AND leader_pid IS NULL) s1 LEFT OUTER JOIN pg_stat_subscription_stats s2 USING(subid); ttl: 10 min_version: 160000 max_version: 190000 tags: [ cluster ] metrics: - subname: { usage: LABEL ,description: "Name of this subscription" } - id: { usage: GAUGE ,description: "OID of the subscription" } - pid: { usage: GAUGE ,description: "Process ID of the subscription leader apply worker" } - received_lsn: { usage: COUNTER ,description: "Last write-ahead log location received" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - apply_error_count: { usage: COUNTER ,description: "Number of times an error occurred while applying changes" } - sync_error_count: { usage: COUNTER ,description: "Number of times an error occurred during the initial table synchronization" } pg_sub_15: name: pg_sub desc: PostgreSQL subscription statistics (15) query: |- SELECT s1.subname, subid AS id, pid, received_lsn, reported_lsn, msg_send_time, msg_recv_time, reported_time, apply_error_count, sync_error_count FROM (SELECT subname, subid, pid, received_lsn - '0/0' AS received_lsn, latest_end_lsn - '0/0' AS reported_lsn, extract(epoch from last_msg_send_time) AS msg_send_time, extract(epoch from last_msg_receipt_time) AS msg_recv_time, extract(epoch from latest_end_time) AS reported_time FROM pg_stat_subscription WHERE relid ISNULL) s1 LEFT OUTER JOIN pg_stat_subscription_stats s2 USING(subid); ttl: 10 min_version: 150000 max_version: 160000 tags: [ cluster ] metrics: - subname: { usage: LABEL ,description: "Name of this subscription" } - id: { usage: GAUGE ,description: "OID of the subscription" } - pid: { usage: GAUGE ,description: "Process ID of the subscription main apply worker process" } - received_lsn: { usage: COUNTER ,description: "Last write-ahead log location received" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } - apply_error_count: { usage: COUNTER ,description: "Number of times an error occurred while applying changes." } - sync_error_count: { usage: COUNTER ,description: "Number of times an error occurred during the initial table synchronization" } pg_sub_10: name: pg_sub desc: PostgreSQL subscription statistics (10-14) query: |- SELECT subname, subid AS id, pid, received_lsn - '0/0' AS received_lsn, latest_end_lsn - '0/0' AS reported_lsn, extract(epoch from last_msg_send_time) AS msg_send_time, extract(epoch from last_msg_receipt_time) AS msg_recv_time, extract(epoch from latest_end_time) AS reported_time FROM pg_stat_subscription WHERE relid ISNULL; ttl: 10 min_version: 100000 max_version: 150000 tags: [ cluster ] metrics: - subname: { usage: LABEL ,description: "Name of this subscription" } - id: { usage: GAUGE ,description: "OID of the subscription" } - pid: { usage: GAUGE ,description: "Process ID of the subscription main apply worker process" } - received_lsn: { usage: COUNTER ,description: "Last write-ahead log location received" } - reported_lsn: { usage: COUNTER ,description: "Last write-ahead log location reported to origin WAL sender" } - msg_send_time: { usage: GAUGE ,description: "Send time of last message received from origin WAL sender" } - msg_recv_time: { usage: GAUGE ,description: "Receipt time of last message received from origin WAL sender" } - reported_time: { usage: GAUGE ,description: "Time of last write-ahead log location reported to origin WAL sender" } #==============================================================# # 0270 pg_origin #==============================================================# # skip by default, require additional privilege setup # GRANT SELECT ON pg_replication_origin, pg_replication_origin_status TO pg_monitor; pg_origin: name: pg_origin desc: PostgreSQL replay state (approximate) for a certain origin query: SELECT roname, remote_lsn - '0/0' AS remote_lsn, local_lsn - '0/0' AS local_lsn FROM pg_replication_origin o LEFT JOIN pg_replication_origin_status os ON o.roident = os.local_id; ttl: 10 min_version: 090500 skip: true tags: [ cluster ] metrics: - roname: { usage: LABEL ,description: "The external, user defined, name of a replication origin." } - remote_lsn: { usage: COUNTER ,description: "The origin node's LSN up to which data has been replicated." } - local_lsn: { usage: COUNTER ,description: "This node's LSN at which remote_lsn has been replicated." } #==============================================================# # 0300 pg_io #==============================================================# pg_io_18: name: pg_io desc: PostgreSQL I/O stats since v18 query: |- SELECT backend_type AS "type",object,context,reads,read_bytes,read_time,writes,write_bytes,write_time,writebacks,writeback_time, extends,extend_bytes,extend_time,hits,evictions,reuses,fsyncs,fsync_time,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_io; ttl: 10 timeout: 1 min_version: 180000 tags: [ cluster ] metrics: - type: { usage: LABEL ,description: "Type of backend" } - object: { usage: LABEL ,description: "Target object of an I/O operation, relation or temp" } - context: { usage: LABEL ,description: "The context of an I/O operation. normal,vacuum,bulkread,bulkwrite" } - reads: { usage: COUNTER ,default: 0 ,description: "Number of read operations, each of the size specified in op_bytes." } - read_bytes: { usage: COUNTER ,default: 0 ,description: "Number of read bytes" } - read_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in read operations in seconds" } - writes: { usage: COUNTER ,default: 0 ,description: "Number of write operations, each of the size specified in op_bytes." } - write_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in write operations in seconds" } - write_bytes: { usage: COUNTER ,default: 0 ,description: "Number of write bytes" } - writebacks: { usage: COUNTER ,default: 0 ,description: "Number of units of size op_bytes which the process requested the kernel write out to permanent storage." } - writeback_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in writeback operations in seconds" } - extends: { usage: COUNTER ,default: 0 ,description: "Number of relation extend operations, each of the size specified in op_bytes." } - extend_bytes: { usage: COUNTER ,default: 0 ,description: "Number of extend bytes" } - extend_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in extend operations in seconds" } - hits: { usage: COUNTER ,default: 0 ,description: "The number of times a desired block was found in a shared buffer." } - evictions: { usage: COUNTER ,default: 0 ,description: "Number of times a block has been written out from a shared or local buffer" } - reuses: { usage: COUNTER ,default: 0 ,description: "The number of times an existing buffer is reused" } - fsyncs: { usage: COUNTER ,default: 0 ,description: "Number of fsync calls. These are only tracked in context normal" } - fsync_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in fsync operations in seconds" } - reset_time: { usage: GAUGE ,description: "Timestamp at which these statistics were last reset" } pg_io_16: name: pg_io desc: PostgreSQL I/O stats query: |- SELECT backend_type AS "type", object, context, reads, read_time,writes,write_time,writebacks,writeback_time,extends, extend_time,hits,evictions,reuses,fsyncs,fsync_time,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_io; ttl: 10 timeout: 1 min_version: 160000 max_version: 180000 tags: [ cluster ] metrics: - type: { usage: LABEL ,description: "Type of backend" } - object: { usage: LABEL ,description: "Target object of an I/O operation, relation or temp" } - context: { usage: LABEL ,description: "The context of an I/O operation. normal,vacuum,bulkread,bulkwrite" } - reads: { usage: COUNTER ,default: 0 ,description: "Number of read operations, each of the size specified in op_bytes." } - read_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in read operations in seconds" } - writes: { usage: COUNTER ,default: 0 ,description: "Number of write operations, each of the size specified in op_bytes." } - write_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in write operations in seconds" } - writebacks: { usage: COUNTER ,default: 0 ,description: "Number of units of size op_bytes which the process requested the kernel write out to permanent storage." } - writeback_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in writeback operations in seconds" } - extends: { usage: COUNTER ,default: 0 ,description: "Number of relation extend operations, each of the size specified in op_bytes." } - extend_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in extend operations in seconds" } - hits: { usage: COUNTER ,default: 0 ,description: "The number of times a desired block was found in a shared buffer." } - evictions: { usage: COUNTER ,default: 0 ,description: "Number of times a block has been written out from a shared or local buffer" } - reuses: { usage: COUNTER ,default: 0 ,description: "The number of times an existing buffer is reused" } - fsyncs: { usage: COUNTER ,default: 0 ,description: "Number of fsync calls. These are only tracked in context normal" } - fsync_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Time spent in fsync operations in seconds" } - reset_time: { usage: GAUGE ,description: "Timestamp at which these statistics were last reset" } #==============================================================# # 0310 pg_size #==============================================================# pg_size: name: pg_size desc: PostgreSQL Database, WAL, Log size since v10 query: |- SELECT datname, pg_database_size(oid) AS bytes FROM pg_database UNION ALL SELECT 'log', CASE WHEN current_setting('logging_collector') = 'on' THEN COALESCE((SELECT SUM(size) FROM pg_catalog.pg_ls_logdir()), 0) ELSE 0 END UNION ALL SELECT 'wal', COALESCE((SELECT SUM(size) FROM pg_catalog.pg_ls_waldir()), 0); ttl: 60 timeout: 1 min_version: 100000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Database name, or special category wal, or log" } - bytes: { usage: GAUGE ,description: "File size in bytes" } #==============================================================# # 0320 pg_archiver #==============================================================# pg_archiver: name: pg_archiver desc: PostgreSQL archiver process statistics query: |- SELECT archived_count AS finish_count,failed_count, extract(epoch FROM last_archived_time) AS finish_time, extract(epoch FROM last_failed_time) AS failed_time, extract(epoch FROM stats_reset) AS reset_time FROM pg_stat_archiver; ttl: 60 min_version: 090400 tags: [ cluster ] metrics: - finish_count: { usage: COUNTER ,description: "Number of WAL files that have been successfully archived" } - failed_count: { usage: COUNTER ,description: "Number of failed attempts for archiving WAL files" } - finish_time: { usage: GAUGE ,description: "Time of the last successful archive operation" } - failed_time: { usage: GAUGE ,description: "Time of the last failed archival operation" } - reset_time: { usage: GAUGE ,description: "Time at which archive statistics were last reset" } #==============================================================# # 0330 pg_bgwriter #==============================================================# # https://pgpedia.info/p/pg_stat_bgwriter.html pg_bgwriter_17: name: pg_bgwriter desc: "PostgreSQL background writer metrics PG 17+" query: SELECT buffers_clean, maxwritten_clean, buffers_alloc, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 170000 tags: [ cluster ] metrics: - buffers_clean: { usage: COUNTER ,description: "Number of buffers written by the background writer" } - maxwritten_clean: { usage: COUNTER ,description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers" } - buffers_alloc: { usage: COUNTER ,description: "Number of buffers allocated" } - reset_time: { usage: GAUGE ,description: "Time at which bgwriter statistics were last reset" } pg_bgwriter_10: name: pg_bgwriter desc: "PostgreSQL background writer metrics (PG 9.4-16)" query: SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint, buffers_clean, buffers_backend, maxwritten_clean, buffers_backend_fsync, buffers_alloc, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 090400 max_version: 170000 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - checkpoint_sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_checkpoint: { usage: COUNTER ,description: "Number of buffers written during checkpoints" } - buffers_clean: { usage: COUNTER ,description: "Number of buffers written by the background writer" } - buffers_backend: { usage: COUNTER ,description: "Number of buffers written directly by a backend" } - maxwritten_clean: { usage: COUNTER ,description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers" } - buffers_backend_fsync: { usage: COUNTER ,description: "Number of times a backend had to execute its own fsync call" } - buffers_alloc: { usage: COUNTER ,description: "Number of buffers allocated" } - reset_time: { usage: GAUGE ,description: "Time at which bgwriter statistics were last reset" } #==============================================================# # 0331 pg_checkpointer #==============================================================# pg_checkpointer_18: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 18+" query: SELECT num_timed, num_requested, num_done, restartpoints_timed, restartpoints_req, restartpoints_done, write_time, sync_time, buffers_written, slru_written, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_checkpointer; ttl: 10 min_version: 180000 tags: [ cluster ] metrics: - num_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - num_requested: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - num_done: { usage: COUNTER ,rename: done ,description: "Number of checkpoints that have been performed" } - restartpoints_timed: { usage: COUNTER ,description: "Number of scheduled restartpoints due to timeout or after a failed attempt to perform it" } - restartpoints_req: { usage: COUNTER ,description: "Number of requested restartpoints" } - restartpoints_done: { usage: COUNTER ,description: "Number of restartpoints that have been performed" } - write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_written: { usage: COUNTER ,description: "Number of buffers written during checkpoints and restartpoints" } - slru_written: { usage: COUNTER ,description: "Number of SLRU buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } pg_checkpointer_17: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 17" query: SELECT num_timed, num_requested, restartpoints_timed, restartpoints_req, restartpoints_done, write_time, sync_time, buffers_written, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_checkpointer; ttl: 10 min_version: 170000 max_version: 180000 tags: [ cluster ] metrics: - num_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - num_requested: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - restartpoints_timed: { usage: COUNTER ,description: "Number of scheduled restartpoints due to timeout or after a failed attempt to perform it" } - restartpoints_req: { usage: COUNTER ,description: "Number of requested restartpoints" } - restartpoints_done: { usage: COUNTER ,description: "Number of restartpoints that have been performed" } - write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_written: { usage: COUNTER ,description: "Number of buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } pg_checkpointer_10: name: pg_checkpointer desc: "PostgreSQL checkpointer stat metrics for pg 9.4-16" query: SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_bgwriter; ttl: 10 min_version: 090400 max_version: 170000 tags: [ cluster ] metrics: - checkpoints_timed: { usage: COUNTER ,rename: timed ,description: "Number of scheduled checkpoints that have been performed" } - checkpoints_req: { usage: COUNTER ,rename: req ,description: "Number of requested checkpoints that have been performed" } - checkpoint_write_time: { usage: COUNTER ,rename: write_time ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in seconds" } - checkpoint_sync_time: { usage: COUNTER ,rename: sync_time ,scale: 1e-3 ,description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in seconds" } - buffers_checkpoint: { usage: COUNTER ,rename: buffers_written ,description: "Number of buffers written during checkpoints and restartpoints" } - reset_time: { usage: GAUGE ,description: "Time at which checkpointer statistics were last reset" } #==============================================================# # 0340 pg_ssl #==============================================================# pg_ssl: name: pg_ssl desc: PostgreSQL SSL client connection count query: | SELECT count(*) FILTER (WHERE ssl) AS enabled, count(*) FILTER ( WHERE NOT ssl) AS disabled FROM pg_stat_ssl; ttl: 10 min_version: 090500 tags: [ cluster ] metrics: - enabled: { usage: GAUGE ,description: "Number of client connection that use ssl" } - disabled: { usage: GAUGE ,description: "Number of client connection that does not use ssl" } #==============================================================# # 0350 pg_checkpoint #==============================================================# pg_checkpoint: name: pg_checkpoint desc: checkpoint information from pg_control_checkpoint since 10 query: |- SELECT checkpoint_lsn - '0/0' AS checkpoint_lsn, redo_lsn - '0/0' AS redo_lsn, timeline_id AS tli, prev_timeline_id AS prev_tli, full_page_writes, split_part(next_xid, ':', 1) AS next_xid_epoch, split_part(next_xid, ':', 2) AS next_xid, next_oid::BIGINT, next_multixact_id::text::BIGINT, next_multi_offset::text::BIGINT, oldest_xid::text::BIGINT, oldest_xid_dbid::text::BIGINT, oldest_active_xid::text::BIGINT, oldest_multi_xid::text::BIGINT, oldest_multi_dbid::BIGINT, oldest_commit_ts_xid::text::BIGINT, newest_commit_ts_xid::text::BIGINT, checkpoint_time AS time, extract(epoch from now() - checkpoint_time) AS elapse FROM pg_control_checkpoint(); ttl: 60 min_version: 100000 tags: [ cluster ] metrics: - checkpoint_lsn: { usage: COUNTER ,description: "Latest checkpoint location" } - redo_lsn: { usage: COUNTER ,description: "Latest checkpoint's REDO location" } - tli: { usage: COUNTER ,description: "Latest checkpoint's TimeLineID" } - prev_tli: { usage: COUNTER ,description: "Latest checkpoint's PrevTimeLineID" } - full_page_writes: { usage: GAUGE ,description: "Latest checkpoint's full_page_writes enabled" } - next_xid_epoch: { usage: COUNTER ,description: "Latest checkpoint's NextXID epoch" } - next_xid: { usage: COUNTER ,description: "Latest checkpoint's NextXID xid" } - next_oid: { usage: COUNTER ,description: "Latest checkpoint's NextOID" } - next_multixact_id: { usage: COUNTER ,description: "Latest checkpoint's NextMultiXactId" } - next_multi_offset: { usage: COUNTER ,description: "Latest checkpoint's NextMultiOffset" } - oldest_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestXID" } - oldest_xid_dbid: { usage: GAUGE ,description: "Latest checkpoint's oldestXID's DB OID" } - oldest_active_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestActiveXID" } - oldest_multi_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestMultiXid" } - oldest_multi_dbid: { usage: GAUGE ,description: "Latest checkpoint's oldestMulti's DB OID" } - oldest_commit_ts_xid: { usage: COUNTER ,description: "Latest checkpoint's oldestCommitTsXid" } - newest_commit_ts_xid: { usage: COUNTER ,description: "Latest checkpoint's newestCommitTsXid" } - time: { usage: COUNTER ,description: "Time of latest checkpoint" } - elapse: { usage: GAUGE ,description: "Seconds elapsed since latest checkpoint in seconds" } #==============================================================# # 0355 pg_timeline #==============================================================# pg_timeline: name: pg_timeline desc: Current timeline ID from primary or replica query: | SELECT COALESCE( (SELECT received_tli FROM pg_stat_wal_receiver), (SELECT timeline_id FROM pg_control_checkpoint()) ) AS id; ttl: 10 min_version: 100000 tags: [ cluster ] metrics: - id: { usage: GAUGE ,description: "Current timeline ID" } #==============================================================# # 0360 pg_recovery #==============================================================# pg_recovery: name: pg_recovery desc: PostgreSQL control recovery metrics (9.6+) query: | SELECT min_recovery_end_timeline AS min_timeline, min_recovery_end_lsn - '0/0' AS min_lsn, backup_start_lsn - '0/0' AS backup_start_lsn, backup_end_lsn - '0/0' AS backup_end_lsn, end_of_backup_record_required AS require_record FROM pg_control_recovery(); ttl: 10 min_version: 090600 tags: [ cluster, replica ] metrics: - min_timeline: { usage: COUNTER ,description: "Min recovery ending loc's timeline" } - min_lsn: { usage: COUNTER ,description: "Minimum recovery ending location" } - backup_start_lsn: { usage: COUNTER ,description: "Backup start location" } - backup_end_lsn: { usage: COUNTER ,description: "Backup end location" } - require_record: { usage: GAUGE ,description: "End-of-backup record required" } pg_recovery_prefetch: name: pg_recovery_prefetch desc: PostgreSQL recovery prefetch metrics (15+) query: SELECT prefetch,hit,skip_init,skip_new,skip_fpw,skip_rep,wal_distance,block_distance,io_depth,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_recovery_prefetch; ttl: 10 min_version: 150000 tags: [ cluster, replica ] metrics: - prefetch: { usage: COUNTER ,description: "Number of blocks prefetched because they were not in the buffer pool" } - hit: { usage: COUNTER ,description: "Number of blocks not prefetched because they were already in the buffer pool" } - skip_init: { usage: COUNTER ,description: "Number of blocks not prefetched because they would be zero-initialized" } - skip_new: { usage: COUNTER ,description: "Number of blocks not prefetched because they didn't exist yet" } - skip_fpw: { usage: COUNTER ,description: "Number of blocks not prefetched because a full page image was included in the WAL" } - skip_rep: { usage: COUNTER ,description: "Number of blocks not prefetched because they were already recently prefetched" } - wal_distance: { usage: GAUGE ,description: "How many bytes ahead the prefetcher is looking" } - block_distance: { usage: GAUGE ,description: "How many blocks ahead the prefetcher is looking" } - io_depth: { usage: GAUGE ,description: "How many prefetches have been initiated but are not yet known to have completed" } - reset_time: { usage: GAUGE ,description: "Time at which these recovery prefetch statistics were last reset" } #==============================================================# # 0370 pg_recovery_state #==============================================================# pg_recovery_state: name: pg_recovery_state desc: PostgreSQL recovery state metrics from pg_stat_recovery (19+) query: |- SELECT promote_triggered, last_replayed_read_lsn - '0/0' AS last_replayed_read_lsn, last_replayed_end_lsn - '0/0' AS last_replayed_end_lsn, last_replayed_tli, replay_end_lsn - '0/0' AS replay_end_lsn, replay_end_tli, extract(EPOCH FROM recovery_last_xact_time) AS recovery_last_xact_time, extract(EPOCH FROM current_chunk_start_time) AS current_chunk_start_time, CASE pause_state WHEN 'not paused' THEN 0 WHEN 'pause requested' THEN 1 WHEN 'paused' THEN 2 ELSE NULL END AS pause_state FROM pg_stat_recovery; ttl: 10 min_version: 190000 tags: [ cluster, replica ] metrics: - promote_triggered: { usage: GAUGE ,description: "True if a promotion has been triggered" } - last_replayed_read_lsn: { usage: COUNTER ,description: "Start WAL location of the last successfully replayed WAL record" } - last_replayed_end_lsn: { usage: COUNTER ,description: "End WAL location of the last successfully replayed WAL record" } - last_replayed_tli: { usage: GAUGE ,description: "Timeline of the last successfully replayed WAL record" } - replay_end_lsn: { usage: COUNTER ,description: "End-plus-one WAL location of the record currently being replayed" } - replay_end_tli: { usage: GAUGE ,description: "Timeline of the WAL record currently being replayed" } - recovery_last_xact_time: { usage: GAUGE ,description: "Unix epoch time of the last transaction commit or abort replayed during recovery" } - current_chunk_start_time: { usage: GAUGE ,description: "Unix epoch time when startup observed replay caught up with the latest received WAL chunk" } - pause_state: { usage: GAUGE ,description: "Recovery pause state encoded as 0 not paused, 1 pause requested, 2 paused" } #==============================================================# # 0380 pg_slru #==============================================================# pg_slru_13: name: pg_slru desc: PostgreSQL simple-least-recently-used (SLRU) cache statistics v13 query: SELECT name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_slru; ttl: 60 min_version: 130000 tags: [ cluster ] metrics: - name: { usage: LABEL ,description: "Name of the SLRU" } - blks_zeroed: { usage: COUNTER ,description: "Number of blocks zeroed during initializations" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the SLRU, so that a read was not necessary" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read for this SLRU" } - blks_written: { usage: COUNTER ,description: "Number of disk blocks written for this SLRU" } - blks_exists: { usage: COUNTER ,description: "Number of blocks checked for existence for this SLRU" } - flushes: { usage: COUNTER ,description: "Number of flushes of dirty data for this SLRU" } - truncates: { usage: COUNTER ,description: "Number of truncates for this SLRU" } - reset_time: { usage: GAUGE ,description: "Time at which these statistics were last reset" } #==============================================================# # 0390 pg_shmem #==============================================================# # pg_shmem require su privilege to work. Disable it or create auxiliary function with su before use: # CREATE OR REPLACE FUNCTION monitor.pg_shmem() RETURNS SETOF pg_shmem_allocations AS $$ SELECT * FROM pg_shmem_allocations;$$ LANGUAGE SQL SECURITY DEFINER; pg_shmem: name: pg_shmem desc: Allocations made from the server's main shared memory segment query: SELECT coalesce(name, 'Free') AS name, off AS offset, size, allocated_size FROM monitor.pg_shmem(); ttl: 60 min_version: 130000 skip: true # disable it by default tags: [cluster, "schema:monitor" ] metrics: - name: { usage: LABEL ,description: "Name of the shared memory allocation" } - offset: { usage: GAUGE ,description: "The offset at which the allocation starts" } - size: { usage: GAUGE ,description: "Size of the allocation" } - allocated_size: { usage: GAUGE ,description: "Size of the allocation including padding" } #==============================================================# # 0400 pg_wal #==============================================================# pg_wal_19: name: pg_wal desc: PostgreSQL WAL statistics since v19 query: SELECT wal_records AS records, wal_fpi AS fpi, wal_bytes AS bytes, wal_fpi_bytes AS fpi_bytes, wal_buffers_full AS buffers_full, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_wal; ttl: 10 tags: [ cluster ] min_version: 190000 metrics: - records: { usage: COUNTER ,description: "Total number of WAL records generated" } - fpi: { usage: COUNTER ,description: "Total number of WAL full page images generated" } - bytes: { usage: COUNTER ,description: "Total amount of WAL generated in bytes" } - fpi_bytes: { usage: COUNTER ,description: "Total amount of WAL full page images generated in bytes" } - buffers_full: { usage: COUNTER ,description: "Number of times WAL data was written to disk because WAL buffers became full" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } pg_wal_18: name: pg_wal desc: PostgreSQL WAL statistics since v18 with some col removed query: SELECT wal_records AS records, wal_fpi AS fpi, wal_bytes AS bytes, wal_buffers_full AS buffers_full,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_wal; ttl: 10 tags: [ cluster ] min_version: 180000 max_version: 190000 metrics: - records: { usage: COUNTER ,description: "Total number of WAL records generated" } - fpi: { usage: COUNTER ,description: "Total number of WAL full page images generated" } - bytes: { usage: COUNTER ,description: "Total amount of WAL generated in bytes" } - buffers_full: { usage: COUNTER ,description: "Number of times WAL data was written to disk because WAL buffers became full" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } pg_wal_14: name: pg_wal desc: PostgreSQL WAL statistics since v14 query: SELECT wal_records AS records, wal_fpi AS fpi, wal_bytes AS bytes, wal_buffers_full AS buffers_full, wal_write AS write, wal_sync AS sync, wal_write_time AS write_time, wal_sync_time AS sync_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_stat_wal; ttl: 10 tags: [ cluster ] min_version: 140000 max_version: 180000 metrics: - records: { usage: COUNTER ,description: "Total number of WAL records generated" } - fpi: { usage: COUNTER ,description: "Total number of WAL full page images generated" } - bytes: { usage: COUNTER ,description: "Total amount of WAL generated in bytes" } - buffers_full: { usage: COUNTER ,description: "Number of times WAL data was written to disk because WAL buffers became full" } - write: { usage: COUNTER ,description: "Number of times WAL buffers were written out to disk via XLogWrite request." } - sync: { usage: COUNTER ,description: "Number of times WAL files were synced to disk via issue_xlog_fsync request" } - write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time spent writing WAL buffers to disk via XLogWrite request in seconds" } - sync_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total amount of time spent syncing WAL files to disk via issue_xlog_fsync request, in seconds" } - reset_time: { usage: GAUGE ,description: "When statistics were last reset" } #==============================================================# # 0410 pg_activity #==============================================================# pg_activity: name: pg_activity desc: PostgreSQL backend activity group by database and state query: |- SELECT datname, state, coalesce(count, 0) AS count, coalesce(max_duration, 0) AS max_duration, coalesce(max_tx_duration, 0) AS max_tx_duration, coalesce(max_conn_duration, 0) AS max_conn_duration FROM (SELECT d.datname, a.state FROM pg_database d, unnest(ARRAY ['active','idle','idle in transaction','idle in transaction (aborted)','fastpath function call','disabled']) a(state) WHERE d.datallowconn AND NOT d.datistemplate) base LEFT JOIN (SELECT datname, state, count(*) AS count, max(extract(epoch from now() - state_change)) AS max_duration, max(extract(epoch from now() - xact_start)) AS max_tx_duration, max(extract(epoch from now() - backend_start)) AS max_conn_duration FROM pg_stat_activity WHERE pid <> pg_backend_pid() GROUP BY 1,2) data USING (datname,state); ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - state: { usage: LABEL ,description: "Current overall state of this backend." } - count: { usage: GAUGE ,description: "Count of connection among (datname,state)" } - max_duration: { usage: GAUGE ,description: "Max duration since last state change among (datname, state)" } - max_tx_duration: { usage: GAUGE ,description: "Max transaction duration since state change among (datname, state)" } - max_conn_duration: { usage: GAUGE ,description: "Max backend session duration since state change among (datname, state)" } #==============================================================# # 0420 pg_wait #==============================================================# pg_wait: name: pg_wait desc: PostgreSQL backend client count group by wait event type since 9.6 query: | SELECT coalesce(datname, '_system') AS datname, coalesce(wait_event_type, 'Running') AS event, count(*) AS count FROM pg_stat_activity GROUP BY 1, 2; ttl: 10 min_version: 090600 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database, _system for global process" } - event: { usage: LABEL ,description: "Wait event type" } - count: { usage: GAUGE ,description: "Count of WaitEvent on target database" } #==============================================================# # 0430 pg_backend #==============================================================# pg_backend: name: pg_backend desc: PostgreSQL backend client count group by wait event type since 10 query: SELECT backend_type AS "type", count(*) AS count FROM pg_stat_activity GROUP BY backend_type; ttl: 10 min_version: 100000 tags: [ cluster ] metrics: - type: { usage: LABEL ,description: "Database backend process type" } - count: { usage: GAUGE ,description: "Database backend process count by backend_type" } #==============================================================# # 0440 pg_xact #==============================================================# pg_xact: name: pg_xact desc: PostgreSQL transaction identifier metrics query: WITH snap(v) AS (SELECT txid_current_snapshot()), xset(v) AS (SELECT txid_snapshot_xip(v) FROM snap), xnum(v) AS (SELECT count(*) from xset), xmin(v) AS (SELECT txid_snapshot_xmin(v) FROM snap), xmax(v) AS (SELECT txid_snapshot_xmax(v) FROM snap) SELECT xmin.v AS xmin, xmax.v AS xmax, xnum.v AS xnum FROM xmin, xmax, xnum; ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - xmin: { usage: COUNTER ,description: "Earliest txid that is still active" } - xmax: { usage: COUNTER ,description: "First as-yet-unassigned txid. txid >= this are invisible." } - xnum: { usage: GAUGE ,description: "Current active transaction count" } #==============================================================# # 0450 pg_xact_age #==============================================================# pg_xact_age: name: pg_xact_age desc: PostgreSQL open transaction age snapshot histograms by database query: |- SELECT datname, greatest(0, extract(epoch FROM now() - xact_start)) AS seconds, CASE WHEN state LIKE 'idle in transaction%' THEN greatest(0, extract(epoch FROM now() - xact_start)) END AS idle_seconds FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND backend_type = 'client backend' AND datname IS NOT NULL AND xact_start IS NOT NULL; ttl: 10 min_version: 100000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } # 1-3-10 log grid from 1s to ~28h; sub-second ages are sampling noise for snapshots. # seconds covers every open transaction, including implicit autocommit ones in # flight, so _count is the current open-transaction count; low buckets absorb # that healthy churn. - seconds: { usage: HISTOGRAM ,bucket: [1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000] ,description: "Open transaction age snapshot in seconds" } # idle_seconds covers the idle-in-transaction subset; capped at 3000s (~50min) # since anything older is uniformly critical and lands in +Inf. - idle_seconds: { usage: HISTOGRAM ,bucket: [1, 3, 10, 30, 100, 300, 1000, 3000] ,description: "Idle-in-transaction age snapshot in seconds" } #==============================================================# # 0460 pg_lock #==============================================================# pg_lock: name: pg_lock desc: PostgreSQL lock distribution by mode and database query: | SELECT datname, mode, coalesce(count, 0) AS count FROM (SELECT d.oid AS database, d.datname, l.mode FROM pg_database d, unnest(ARRAY ['AccessShareLock','RowShareLock','RowExclusiveLock','ShareUpdateExclusiveLock', 'ShareLock','ShareRowExclusiveLock','ExclusiveLock','AccessExclusiveLock']) l(mode) WHERE d.datallowconn AND NOT d.datistemplate) base LEFT JOIN (SELECT database, mode, count(*) AS count FROM pg_locks WHERE database IS NOT NULL GROUP BY 1, 2) cnt USING (database, mode); ttl: 10 min_version: 090400 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database this backend is connected to" } - mode: { usage: LABEL ,description: "Name of the lock mode held or desired by this process" } - count: { usage: GAUGE ,description: "Number of locks of corresponding mode and database" } #==============================================================# # 0470 pg_lock_stat #==============================================================# pg_lock_stat: name: pg_lock_stat desc: PostgreSQL cluster-wide lock statistics by lock type (19+) query: |- SELECT locktype, waits, wait_time, fastpath_exceeded FROM pg_stat_lock; ttl: 10 min_version: 190000 tags: [ cluster ] metrics: - locktype: { usage: LABEL ,description: "Type of the lockable object" } - waits: { usage: COUNTER ,description: "Number of times a lock of this type was acquired after waiting longer than deadlock_timeout because of a conflicting lock" } - wait_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent waiting for locks of this type in seconds, only counting waits longer than deadlock_timeout (scaled from PG milliseconds)" } - fastpath_exceeded: { usage: COUNTER ,description: "Number of times a lock of this type could not be acquired via fast path because the slot limit was exceeded" } #==============================================================# # 0480 pg_query #==============================================================# pg_query_17: name: pg_query desc: PostgreSQL Query metrics, require pg_stat_statements installed, 17+ query: |- SELECT datname, queryid AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_exec_time) AS exec_time, sum(shared_blk_read_time) + sum(shared_blk_write_time) AS io_time, sum(wal_bytes) AS wal_bytes ,sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, sum(shared_blks_dirtied) AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements(false) s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 170000 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "QueryID generated from internal hash code, computed from the statement's parse tree" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds" } - wal_bytes: { usage: COUNTER ,description: "Total amount of WAL bytes generated by the statement" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } pg_query_13: name: pg_query desc: PostgreSQL Query metrics, require pg_stat_statements installed, 13 - 16 query: |- SELECT datname, queryid AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_exec_time) AS exec_time, sum(blk_read_time) + sum(blk_write_time) AS io_time, sum(wal_bytes) AS wal_bytes ,sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, sum(shared_blks_dirtied) AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements(false) s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 130000 max_version: 170000 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "QueryID generated from internal hash code, computed from the statement's parse tree" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds" } - wal_bytes: { usage: COUNTER ,description: "Total amount of WAL bytes generated by the statement" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } pg_query_10: name: pg_query desc: PostgreSQL query statement metrics, require pg_stat_statements installed, 9.4 ~ 12 query: |- SELECT datname, queryid AS query, sum(calls) AS calls, sum(rows) AS rows, sum(total_time) AS exec_time, sum(blk_read_time) + sum(blk_write_time) AS io_time, sum(shared_blks_hit) AS sblk_hit, sum(shared_blks_read) AS sblk_read, sum(shared_blks_dirtied) AS sblk_dirtied, sum(shared_blks_written) AS sblk_written FROM pg_stat_statements(false) s JOIN pg_database d ON s.dbid = d.oid WHERE userid != 10 AND calls > 4 GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 128; ttl: 10 timeout: 2 min_version: 090400 max_version: 130000 tags: [ cluster, "extension:pg_stat_statements" ] metrics: - datname: { usage: LABEL ,description: "Name of database" } - query: { usage: LABEL ,description: "QueryID generated from internal hash code, computed from the statement's parse tree" } - calls: { usage: COUNTER ,description: "Number of times the statement was executed" } - rows: { usage: COUNTER ,description: "Total number of rows retrieved or affected by the statement" } - exec_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent executing the statement, in seconds" } - io_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time the statement spent reading and writing blocks, in seconds" } - sblk_hit: { usage: COUNTER ,description: "Total number of shared block cache hits by the statement" } - sblk_read: { usage: COUNTER ,description: "Total number of shared blocks read by the statement" } - sblk_dirtied: { usage: COUNTER ,description: "Total number of shared blocks dirtied by the statement" } - sblk_written: { usage: COUNTER ,description: "Total number of shared blocks written by the statement" } #==============================================================# # 0510 pg_vacuuming #==============================================================# pg_vacuuming_18: name: pg_vacuuming desc: PostgreSQL vacuum progress 18+ query: |- SELECT datname, pid, relid::RegClass AS relname, CASE phase WHEN 'scanning heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_scanned / heap_blks_total ELSE 0.0 END) WHEN 'vacuuming heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_vacuumed / heap_blks_total ELSE 0 END) ELSE NULL END AS progress, indexes_total, indexes_processed, dead_tuple_bytes, delay_time FROM pg_stat_progress_vacuum; ttl: 10 min_version: 180000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "database name" } - pid: { usage: LABEL ,description: "process id of vacuum worker" } - relname: { usage: LABEL ,description: "relation name of vacuuming table" } - progress: { usage: GAUGE ,description: "vacuum progress ratio (0-1) based on heap blocks scanned/vacuumed" } - indexes_total: { usage: GAUGE ,description: "total number of indexes that will be vacuumed or cleaned up" } - indexes_processed: { usage: GAUGE ,description: "number of indexes that have been vacuumed or cleaned up" } - dead_tuple_bytes: { usage: GAUGE ,description: "total size of dead tuples collected since the beginning of vacuum in bytes" } - delay_time: { usage: COUNTER ,scale: 1e-3 ,description: "total time spent sleeping due to cost-based delay in seconds" } pg_vacuuming_17: name: pg_vacuuming desc: PostgreSQL vacuum progress 17 (with index progress tracking) query: |- SELECT datname, pid, relid::RegClass AS relname, CASE phase WHEN 'scanning heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_scanned / heap_blks_total ELSE 0.0 END) WHEN 'vacuuming heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_vacuumed / heap_blks_total ELSE 0 END) ELSE NULL END AS progress, indexes_total, indexes_processed, dead_tuple_bytes FROM pg_stat_progress_vacuum; ttl: 10 min_version: 170000 max_version: 180000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "database name" } - pid: { usage: LABEL ,description: "process id of vacuum worker" } - relname: { usage: LABEL ,description: "relation name of vacuuming table" } - progress: { usage: GAUGE ,description: "vacuum progress ratio (0-1) based on heap blocks scanned/vacuumed" } - indexes_total: { usage: GAUGE ,description: "total number of indexes that will be vacuumed or cleaned up" } - indexes_processed: { usage: GAUGE ,description: "number of indexes that have been vacuumed or cleaned up" } - dead_tuple_bytes: { usage: GAUGE ,description: "total size of dead tuples collected since the beginning of vacuum in bytes" } pg_vacuuming_12: name: pg_vacuuming desc: PostgreSQL vacuum progress 12-16 query: |- SELECT datname, pid, relid::RegClass AS relname, CASE phase WHEN 'scanning heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_scanned / heap_blks_total ELSE 0.0 END) WHEN 'vacuuming heap' THEN (CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_vacuumed / heap_blks_total ELSE 0 END) ELSE NULL END AS progress FROM pg_stat_progress_vacuum; ttl: 10 min_version: 120000 max_version: 170000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "database name" } - pid: { usage: LABEL ,description: "process id of vacuum worker" } - relname: { usage: LABEL ,description: "relation name of vacuuming table" } - progress: { usage: GAUGE ,description: "vacuum progress ratio (0-1) based on heap blocks scanned/vacuumed" } #==============================================================# # 0520 pg_indexing #==============================================================# pg_indexing: name: pg_indexing desc: PostgreSQL index creating progress (v12+) query: |- SELECT datname, pid, relid::RegClass AS relname, (CASE WHEN blocks_total > 0 THEN 1.0 * blocks_done / blocks_total ELSE NULL END) AS blocks, (CASE WHEN tuples_total > 0 THEN 1.0 * tuples_done / tuples_total ELSE NULL END) AS tuples, (CASE WHEN partitions_total > 0 THEN 1.0 * partitions_done / partitions_total ELSE NULL END) AS partitions, (CASE WHEN lockers_total > 0 THEN 1.0 * lockers_done / lockers_total ELSE NULL END) AS lockers FROM pg_stat_progress_create_index pspci; ttl: 10 min_version: 120000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - pid: { usage: LABEL ,description: "Process id of indexing table" } - relname: { usage: LABEL ,description: "Relation name of indexed table" } - blocks: { usage: GAUGE ,description: "Percent of blocks been proceeded" } - tuples: { usage: GAUGE ,description: "Percent of tuples been proceeded" } - partitions: { usage: GAUGE ,description: "Percent of partitions been proceeded" } - lockers: { usage: GAUGE ,description: "Percent of lockers been proceeded" } #==============================================================# # 0530 pg_clustering #==============================================================# pg_clustering: name: pg_clustering desc: PostgreSQL cluster or vacuum full progress (v12+) query: SELECT datname, pid, relid::RegClass AS relname, heap_tuples_scanned AS tup_scan, CASE WHEN heap_blks_total > 0 THEN 1.0 * heap_blks_scanned / heap_blks_total ELSE 0 END AS progress FROM pg_stat_progress_cluster; ttl: 10 min_version: 120000 tags: [ cluster, primary ] metrics: - datname: { usage: LABEL ,description: "Name of database been clustering" } - pid: { usage: LABEL ,description: "Process id of indexing table" } - relname: { usage: LABEL ,description: "Relation name of indexed table" } - tup_scan: { usage: GAUGE ,description: "How much tuple been scanned" } - progress: { usage: GAUGE ,description: "Progress of heap been processed" } #==============================================================# # 0540 pg_backup #==============================================================# pg_backup: name: pg_backup desc: PostgreSQL basebackup progress since 13 query: SELECT pid, param1 AS phase, CASE param2 WHEN -1::integer THEN NULL::bigint ELSE param2 END AS total_bytes, param3 AS sent_bytes FROM pg_stat_get_progress_info('BASEBACKUP'); ttl: 10 min_version: 130000 tags: [ cluster ] metrics: - pid: { usage: LABEL ,description: "process id of basebackup sender" } - phase: { usage: GAUGE ,description: "Phase encoded in 0~5 initial, wait checkpoint, estimate, streaming, waiting archive, transfer archive" } - total_bytes: { usage: GAUGE ,description: "Total amount of data that will be streamed" } - sent_bytes: { usage: GAUGE ,description: "Amount of data streamed" } #==============================================================# # 0610 pg_db #==============================================================# pg_db_18: name: pg_db desc: PostgreSQL database stats from pg_stat_database v18 query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total,blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,coalesce(checksum_failures, -1) AS cks_fails, checksum_last_failure AS cks_fail_time,blk_read_time,blk_write_time, session_time,active_time,idle_in_transaction_time AS ixact_time,sessions,sessions_abandoned,sessions_fatal,sessions_killed,parallel_workers_to_launch,parallel_workers_launched, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 180000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - cks_fails: { usage: COUNTER ,description: "Number of data page checksum failures detected in this database, -1 for not enabled" } - cks_fail_time: { usage: GAUGE ,description: "Time at which the last data page checksum failure was detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - session_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent by database sessions in this database, in seconds" } - active_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent executing SQL statements in this database, in seconds" } - ixact_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent idling while in a transaction in this database, in seconds" } - sessions: { usage: COUNTER ,description: "Total number of sessions established to this database" } - sessions_abandoned: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated because connection to the client was lost" } - sessions_fatal: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated by fatal errors" } - sessions_killed: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated by operator intervention" } - parallel_workers_to_launch: { usage: COUNTER ,description: "Number of parallel workers planned to be launched by queries on this database" } - parallel_workers_launched: { usage: COUNTER ,description: "Number of parallel workers launched by queries on this database" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_db_14: name: pg_db desc: PostgreSQL database stats from pg_stat_database v14 (with 7 new time & session metrics) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total,blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,coalesce(checksum_failures, -1) AS cks_fails, checksum_last_failure AS cks_fail_time,blk_read_time,blk_write_time, session_time,active_time,idle_in_transaction_time AS ixact_time,sessions,sessions_abandoned,sessions_fatal,sessions_killed,extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 140000 max_version: 180000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - cks_fails: { usage: COUNTER ,description: "Number of data page checksum failures detected in this database, -1 for not enabled" } - cks_fail_time: { usage: GAUGE ,description: "Time at which the last data page checksum failure was detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - session_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent by database sessions in this database, in seconds" } - active_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent executing SQL statements in this database, in seconds" } - ixact_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent idling while in a transaction in this database, in seconds" } - sessions: { usage: COUNTER ,description: "Total number of sessions established to this database" } - sessions_abandoned: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated because connection to the client was lost" } - sessions_fatal: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated by fatal errors" } - sessions_killed: { usage: COUNTER ,description: "Number of database sessions to this database that were terminated by operator intervention" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_db_12: name: pg_db desc: PostgreSQL database stats from pg_stat_database v12 v13 (with 2 new checksum metrics) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total,blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,coalesce(checksum_failures, -1) AS cks_fails, checksum_last_failure AS cks_fail_time,blk_read_time,blk_write_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 120000 max_version: 140000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - cks_fails: { usage: COUNTER ,description: "Number of data page checksum failures detected in this database, -1 for not enabled" } - cks_fail_time: { usage: GAUGE ,description: "Time at which the last data page checksum failure was detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } pg_db_10: name: pg_db desc: PostgreSQL database stats from pg_stat_database v10 v11 (actually since 9.2) query: |- SELECT d.datname, datid,age(datfrozenxid) AS age, datistemplate AS is_template, datallowconn AS allow_conn, datconnlimit AS conn_limit, datfrozenxid::TEXT::BIGINT as frozen_xid, numbackends,xact_commit,xact_rollback,xact_rollback + xact_commit AS xact_total, blks_read,blks_hit,blks_read + blks_hit AS blks_access,tup_returned,tup_fetched,tup_inserted,tup_updated,tup_deleted,tup_inserted + tup_updated + tup_deleted AS tup_modified, conflicts,temp_files,temp_bytes,deadlocks,blk_read_time,blk_write_time, extract(EPOCH FROM stats_reset) AS reset_time FROM pg_database d JOIN pg_stat_database sd ON d.oid = sd.datid; ttl: 10 min_version: 090200 max_version: 120000 tags: [ cluster ] metrics: - datname: { usage: LABEL ,description: "Name of the database" } - datid: { usage: GAUGE ,description: "OID of the database" } - age: { usage: GAUGE ,description: "Age of database calculated from datfrozenxid" } - is_template: { usage: GAUGE ,description: "If true(1), then this database can be cloned by any user with CREATEDB privileges" } - allow_conn: { usage: GAUGE ,description: "If false(0) then no one can connect to this database." } - conn_limit: { usage: GAUGE ,description: "Sets maximum number of concurrent connections that can be made to this database. -1 means no limit." } - frozen_xid: { usage: GAUGE ,description: "All transaction IDs before this one have been frozen" } - numbackends: { usage: GAUGE ,description: "Number of backends currently connected to this database" } - xact_commit: { usage: COUNTER ,description: "Number of transactions in this database that have been committed" } - xact_rollback: { usage: COUNTER ,description: "Number of transactions in this database that have been rolled back" } - xact_total: { usage: COUNTER ,description: "Number of transactions in this database" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read in this database" } - blks_hit: { usage: COUNTER ,description: "Number of times disk blocks were found already in the buffer cache" } - blks_access: { usage: COUNTER ,description: "Number of times disk blocks that accessed read+hit" } - tup_returned: { usage: COUNTER ,description: "Number of rows returned by queries in this database" } - tup_fetched: { usage: COUNTER ,description: "Number of rows fetched by queries in this database" } - tup_inserted: { usage: COUNTER ,description: "Number of rows inserted by queries in this database" } - tup_updated: { usage: COUNTER ,description: "Number of rows updated by queries in this database" } - tup_deleted: { usage: COUNTER ,description: "Number of rows deleted by queries in this database" } - tup_modified: { usage: COUNTER ,description: "Number of rows modified by queries in this database" } - conflicts: { usage: COUNTER ,description: "Number of queries canceled due to conflicts with recovery in this database" } - temp_files: { usage: COUNTER ,description: "Number of temporary files created by queries in this database" } - temp_bytes: { usage: COUNTER ,description: "Total amount of data written to temporary files by queries in this database." } - deadlocks: { usage: COUNTER ,description: "Number of deadlocks detected in this database" } - blk_read_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent reading data file blocks by backends in this database, in seconds" } - blk_write_time: { usage: COUNTER ,scale: 1e-3 ,description: "Time spent writing data file blocks by backends in this database, in seconds" } - reset_time: { usage: GAUGE ,description: "Time at which database statistics were last reset" } #==============================================================# # 0620 pg_db_confl #==============================================================# # https://pgpedia.info/p/pg_stat_database_conflicts.html pg_db_confl_16: name: pg_db_confl desc: PostgreSQL database conflicts metrics for PG16+ query: SELECT datid, datname, confl_tablespace, confl_lock, confl_snapshot, confl_bufferpin, confl_deadlock, confl_active_logicalslot FROM pg_stat_database_conflicts; ttl: 10 min_version: 160000 tags: [ cluster, replica ] metrics: - datid: { usage: DISCARD } - datname: { usage: LABEL ,description: "Name of this database" } - confl_tablespace: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to dropped tablespaces" } - confl_lock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to lock timeouts" } - confl_snapshot: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to old snapshots" } - confl_bufferpin: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to pinned buffers" } - confl_deadlock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to deadlocks" } - confl_active_logicalslot: { usage: COUNTER ,description: "Number of uses of logical slots in this database that have been canceled due to old snapshots or too low a wal_level on the primary" } pg_db_confl_15: name: pg_db_confl desc: PostgreSQL database conflicts metrics for pg 9.1 - 15 query: SELECT datid, datname, confl_tablespace, confl_lock, confl_snapshot, confl_bufferpin, confl_deadlock FROM pg_stat_database_conflicts; ttl: 10 min_version: 90100 max_version: 160000 tags: [ cluster, replica ] metrics: - datid: { usage: DISCARD } - datname: { usage: LABEL ,description: "Name of this database" } - confl_tablespace: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to dropped tablespaces" } - confl_lock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to lock timeouts" } - confl_snapshot: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to old snapshots" } - confl_bufferpin: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to pinned buffers" } - confl_deadlock: { usage: COUNTER ,description: "Number of queries in this database that have been canceled due to deadlocks" } #==============================================================# # 0640 pg_pubrel #==============================================================# pg_pubrel: name: pg_pubrel desc: PostgreSQL publication and relation count query: SELECT CURRENT_CATALOG AS datname, pubname, count(*) AS count FROM pg_publication p, LATERAL pg_get_publication_tables(pubname) GROUP BY pubname; ttl: 10 min_version: 100000 metrics: - datname: { usage: LABEL ,description: "Name of the database which publication belonged" } - pubname: { usage: LABEL ,description: "Name of the publication" } - count: { usage: GAUGE ,description: "Count of relation in the publication" } #==============================================================# # 0650 pg_subrel #==============================================================# pg_subrel: name: pg_subrel desc: PostgreSQL subscripted relation group by state query: SELECT CURRENT_CATALOG AS datname, ss.subname, sr.srsubstate::TEXT AS state, count(*) AS count FROM pg_catalog.pg_subscription_rel AS sr JOIN (SELECT DISTINCT subid, subname FROM pg_catalog.pg_stat_subscription) AS ss ON ss.subid = sr.srsubid GROUP BY ss.subname, sr.srsubstate; ttl: 10 min_version: 100000 metrics: - datname: { usage: LABEL ,description: "Name of the database which publication belonged" } - subname: { usage: LABEL ,description: "Name of the subscription" } - state: { usage: LABEL ,description: "State of table in subscription, i=initialize, d=data copy, s=sync, r=ready" } - count: { usage: GAUGE ,description: "Count of relation in this subscription and corresponding state" } #==============================================================# # 0660 pg_vacuum_score #==============================================================# pg_vacuum_score: name: pg_vacuum_score desc: PostgreSQL current database autovacuum score summary from pg_stat_autovacuum_scores (19+) query: |- SELECT CURRENT_CATALOG AS datname, COALESCE(max(score), 0) AS max_score, COALESCE(max(xid_score), 0) AS max_xid_score, COALESCE(max(mxid_score), 0) AS max_mxid_score, COALESCE(max(vacuum_score), 0) AS max_vacuum_score, COALESCE(max(vacuum_insert_score), 0) AS max_vacuum_insert_score, COALESCE(max(analyze_score), 0) AS max_analyze_score, count(*) AS table_count, count(*) FILTER (WHERE do_vacuum OR do_analyze) AS candidate_count, count(*) FILTER (WHERE do_vacuum) AS vacuum_candidate_count, count(*) FILTER (WHERE do_analyze) AS analyze_candidate_count, count(*) FILTER (WHERE for_wraparound) AS wraparound_candidate_count FROM pg_stat_autovacuum_scores; ttl: 10 timeout: 1 min_version: 190000 tags: [ primary ] metrics: - datname: { usage: LABEL ,description: "Name of the current database" } - max_score: { usage: GAUGE ,description: "Maximum autovacuum priority score across tables in the current database" } - max_xid_score: { usage: GAUGE ,description: "Maximum transaction ID age component score across tables in the current database" } - max_mxid_score: { usage: GAUGE ,description: "Maximum multixact ID age component score across tables in the current database" } - max_vacuum_score: { usage: GAUGE ,description: "Maximum vacuum component score across tables in the current database" } - max_vacuum_insert_score: { usage: GAUGE ,description: "Maximum vacuum insert component score across tables in the current database" } - max_analyze_score: { usage: GAUGE ,description: "Maximum analyze component score across tables in the current database" } - table_count: { usage: GAUGE ,description: "Number of tables represented in pg_stat_autovacuum_scores for the current database, including TOAST tables" } - candidate_count: { usage: GAUGE ,description: "Number of tables that autovacuum would vacuum or analyze at query time" } - vacuum_candidate_count: { usage: GAUGE ,description: "Number of tables that autovacuum would vacuum at query time" } - analyze_candidate_count: { usage: GAUGE ,description: "Number of tables that autovacuum would analyze at query time" } - wraparound_candidate_count: { usage: GAUGE ,description: "Number of tables that autovacuum would vacuum for wraparound prevention at query time" } #==============================================================# # 0700 pg_table #==============================================================# pg_table_18: name: pg_table desc: PostgreSQL table metrics v18+ query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_tup_newpage_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.n_ins_since_vacuum,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze,psut.last_seq_scan, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psut.total_vacuum_time AS vacuum_time,psut.total_autovacuum_time AS autovacuum_time,psut.total_analyze_time AS analyze_time,psut.total_autoanalyze_time AS autoanalyze_time, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 180000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116,p/partitioned/112" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_tup_newpage_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated where the successor version goes onto a new heap page" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - n_ins_since_vacuum: { usage: GAUGE ,description: "Estimated number of rows inserted since this table was last vacuumed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - last_seq_scan: { usage: DISCARD ,description: "The timestamp of the last seq scan on this table" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - vacuum_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Total time this table has been manually vacuumed, in seconds" } - autovacuum_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Total time this table has been vacuumed by the autovacuum daemon, in seconds" } - analyze_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Total time this table has been manually analyzed, in seconds" } - autoanalyze_time: { usage: COUNTER ,default: 0 ,scale: 1e-3 ,description: "Total time this table has been analyzed by the autovacuum daemon, in seconds" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_table_16: name: pg_table desc: PostgreSQL table metrics 16-17 query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_tup_newpage_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.n_ins_since_vacuum,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze,psut.last_seq_scan, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 160000 max_version: 180000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116,p/partitioned/112" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_tup_newpage_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated where the successor version goes onto a new heap page" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - n_ins_since_vacuum: { usage: GAUGE ,description: "Estimated number of rows inserted since this table was last vacuumed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - last_seq_scan: { usage: DISCARD ,description: "The timestamp of the last seq scan on this table" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_table_13: name: pg_table desc: PostgreSQL table metrics 13-15 (with n_ins_since_vacuum) query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.n_ins_since_vacuum,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 130000 max_version: 160000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116,p/partitioned/112" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - n_ins_since_vacuum: { usage: GAUGE ,description: "Estimated number of rows inserted since this table was last vacuumed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } pg_table_10: name: pg_table desc: PostgreSQL table metrics 9.4-12 query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || c.relname AS relname, c.oid AS relid, ascii(c.relkind) AS kind, c.relpages AS pages, c.reltuples AS tuples, c.relfrozenxid AS frozenxid, age(c.relfrozenxid) AS age, c.relnatts AS ncols, psut.seq_scan,psut.seq_tup_read,psut.idx_scan,psut.idx_tup_fetch,psut.seq_scan + psut.idx_scan AS tbl_scan, psut.seq_tup_read + psut.idx_tup_fetch AS tup_read, psut.n_tup_ins,psut.n_tup_upd,psut.n_tup_del,(psut.n_tup_ins + psut.n_tup_upd + psut.n_tup_del) AS n_tup_mod,psut.n_tup_hot_upd,psut.n_live_tup,psut.n_dead_tup, psut.n_mod_since_analyze,psut.last_vacuum,psut.last_autovacuum,psut.last_analyze,psut.last_autoanalyze, psut.vacuum_count,psut.autovacuum_count,psut.analyze_count,psut.autoanalyze_count, psio.heap_blks_read,psio.heap_blks_hit,psio.idx_blks_read,psio.idx_blks_hit,psio.toast_blks_read,psio.toast_blks_hit,psio.tidx_blks_read,psio.tidx_blks_hit FROM pg_class c JOIN pg_namespace nsp ON c.relnamespace = nsp.oid LEFT JOIN pg_stat_user_tables psut ON psut.relid = c.oid LEFT JOIN pg_statio_user_tables psio ON psio.relid = c.oid WHERE nsp.nspname !~ '^pg_' AND nsp.nspname !~ '^_' AND nsp.nspname !~ '^timescaledb' AND nsp.nspname !~ '^citus' AND nsp.nspname !~ '^columnar' AND nsp.nspname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') AND c.relkind = ANY (ARRAY ['r','m','t','p']) ORDER BY c.relpages DESC LIMIT 256; ttl: 10 timeout: 2 min_version: 090400 max_version: 130000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Relation name of this table" } - relid: { usage: GAUGE ,description: "Relation oid of this table" } - kind: { usage: GAUGE ,description: "Relation kind r/table/114,m/mview/109,t/toast/116,p/partitioned/112" } - pages: { usage: GAUGE ,description: "Size of the on-disk representation of this table in pages" } - tuples: { usage: GAUGE ,description: "Estimated number of rows in this table" } - frozenxid: { usage: GAUGE ,description: "All txid before this have been frozen on this table" } - age: { usage: GAUGE ,description: "Age of this table in vacuum cycles" } - ncols: { usage: GAUGE ,description: "Number of columns in the table" } - seq_scan: { usage: COUNTER ,default: 0 ,description: "Number of sequential scans initiated on this table" } - seq_tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by sequential scans" } - idx_scan: { usage: COUNTER ,default: 0 ,description: "Number of index scans initiated on this table" } - idx_tup_fetch: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by index scans" } - tbl_scan: { usage: COUNTER ,default: 0 ,description: "Number of scans initiated on this table" } - tup_read: { usage: COUNTER ,default: 0 ,description: "Number of live rows fetched by scans" } - n_tup_ins: { usage: COUNTER ,default: 0 ,description: "Number of rows inserted" } - n_tup_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows updated (includes HOT updated rows)" } - n_tup_del: { usage: COUNTER ,default: 0 ,description: "Number of rows deleted" } - n_tup_mod: { usage: COUNTER ,default: 0 ,description: "Number of rows modified (insert + update + delete)" } - n_tup_hot_upd: { usage: COUNTER ,default: 0 ,description: "Number of rows HOT updated (i.e with no separate index update required)" } - n_live_tup: { usage: GAUGE ,description: "Estimated number of live rows" } - n_dead_tup: { usage: GAUGE ,description: "Estimated number of dead rows" } - n_mod_since_analyze: { usage: GAUGE ,description: "Estimated number of rows modified since this table was last analyzed" } - last_vacuum: { usage: DISCARD ,description: "Last time at which this table was manually vacuumed (not counting VACUUM FULL)" } - last_autovacuum: { usage: DISCARD ,description: "Last time at which this table was vacuumed by the autovacuum daemon" } - last_analyze: { usage: DISCARD ,description: "Last time at which this table was manually analyzed" } - last_autoanalyze: { usage: DISCARD ,description: "Last time at which this table was analyzed by the autovacuum daemon" } - vacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually vacuumed (not counting VACUUM FULL)" } - autovacuum_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been vacuumed by the autovacuum daemon" } - analyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been manually analyzed" } - autoanalyze_count: { usage: COUNTER ,default: 0 ,description: "Number of times this table has been analyzed by the autovacuum daemon" } - heap_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from this table" } - heap_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in this table" } - idx_blks_read: { usage: COUNTER ,default: 0 ,description: "Number of disk blocks read from all indexes on this table" } - idx_blks_hit: { usage: COUNTER ,default: 0 ,description: "Number of buffer hits in all indexes on this table" } - toast_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table (if any)" } - toast_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table (if any)" } - tidx_blks_read: { usage: DISCARD ,default: 0 ,description: "Number of disk blocks read from this table's TOAST table indexes (if any)" } - tidx_blks_hit: { usage: DISCARD ,default: 0 ,description: "Number of buffer hits in this table's TOAST table indexes (if any)" } #==============================================================# # 0710 pg_index #==============================================================# pg_index: name: pg_index desc: PostgreSQL index metrics query: |- SELECT CURRENT_CATALOG AS datname, schemaname || '.' || indexrelname AS idxname, schemaname || '.' || relname AS relname ,indexrelid AS relid, relpages, reltuples, idx_scan, idx_tup_read, idx_tup_fetch, idx_blks_read, idx_blks_hit FROM pg_stat_user_indexes psui, LATERAL (SELECT idx_blks_read, idx_blks_hit FROM pg_statio_user_indexes psio WHERE psio.indexrelid = psui.indexrelid LIMIT 1) p2, LATERAL (SELECT relpages,reltuples FROM pg_class c WHERE c.oid = psui.indexrelid LIMIT 1) p3 WHERE schemaname !~ '^pg_' AND schemaname !~ '^_' AND schemaname !~ '^timescaledb' AND schemaname !~ '^citus' AND schemaname !~ '^columnar' AND schemaname NOT IN ('pg_catalog','information_schema','pg_toast','repack','monitor') ORDER BY idx_tup_read DESC LIMIT 512; ttl: 10 timeout: 1 min_version: 090400 metrics: - datname: { usage: LABEL ,description: "Database name of this index" } - idxname: { usage: LABEL ,description: "Name of this index (full-qualified schema name)" } - relname: { usage: LABEL ,description: "Name of the table for this index (full-qualified schema name)" } - relid: { usage: LABEL ,description: "Relation oid of this index" } - relpages: { usage: GAUGE ,description: "Size of the on-disk representation of this index in pages" } - reltuples: { usage: GAUGE ,description: "Estimate relation tuples" } - idx_scan: { usage: COUNTER ,description: "Number of index scans initiated on this index" } - idx_tup_read: { usage: COUNTER ,description: "Number of index entries returned by scans on this index" } - idx_tup_fetch: { usage: COUNTER ,description: "Number of live table rows fetched by simple index scans using this index" } - idx_blks_read: { usage: COUNTER ,description: "Number of disk blocks read from this index" } - idx_blks_hit: { usage: COUNTER ,description: "Number of buffer hits in this index" } #==============================================================# # 0720 pg_func #==============================================================# pg_func: desc: PostgreSQL function metrics query: SELECT CURRENT_CATALOG AS datname, schemaname || '.' || funcname AS funcname, sum(calls) AS calls, sum(total_time) AS total_time, sum(self_time) AS self_time FROM pg_stat_user_functions GROUP BY 2 ORDER BY 4 DESC LIMIT 128; ttl: 10 min_version: 090400 metrics: - datname: { usage: LABEL ,description: "Name of belonged database" } - funcname: { usage: LABEL ,description: "Name of this function, may have multiple override" } - calls: { usage: COUNTER ,description: "Number of times this function has been called" } - total_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent in this function and all other functions called by it, in seconds" } - self_time: { usage: COUNTER ,scale: 1e-3 ,description: "Total time spent in this function itself, not including other functions called by it, in seconds" } #==============================================================# # 0730 pg_seq #==============================================================# pg_seq: desc: PostgreSQL sequence metrics query: SELECT CURRENT_CATALOG AS datname, schemaname || '.' || sequencename AS seqname, last_value, blks_read, blks_hit FROM pg_sequences s, LATERAL (SELECT relid, blks_read, blks_hit FROM pg_statio_all_sequences sio WHERE s.schemaname = sio.schemaname AND s.sequencename = sio.relname LIMIT 1) d LIMIT 128; ttl: 10 min_version: 100000 metrics: - datname: { usage: LABEL ,description: "Database name of this sequence" } - seqname: { usage: LABEL ,description: "Fully schema qualified sequence name" } - last_value: { usage: COUNTER ,description: "The last sequence value written to disk" } - blks_read: { usage: COUNTER ,description: "Number of disk blocks read from this sequence" } - blks_hit: { usage: COUNTER ,description: "Number of buffer hits in this sequence" } #==============================================================# # 0740 pg_relkind #==============================================================# pg_relkind: name: pg_relkind desc: Postgres relation count by kind (category, r,i,m,t,...) query: SELECT CURRENT_CATALOG AS datname, relkind, count(*) AS count FROM pg_class GROUP BY relkind; ttl: 60 timeout: 1 min_version: 090400 metrics: - datname: { usage: LABEL ,description: "Name of database" } - relkind: { usage: LABEL ,description: "Kind of this relation, could be r,i,S,t,v,m,c,f,p,I" } - count: { usage: GAUGE ,description: "Number of relations of corresponding relkind" } #==============================================================# # 0750 pg_defpart #==============================================================# pg_defpart: name: pg_defpart desc: PostgreSQL default partition tuples query: SELECT CURRENT_CATALOG AS datname, relnamespace::RegNamespace || '.' || relname AS relname, reltuples AS tuples FROM pg_class WHERE relpartbound IS NOT NULL AND pg_catalog.pg_get_expr(relpartbound, oid) = 'DEFAULT' ORDER BY reltuples DESC LIMIT 64; ttl: 60 timeout: 1 min_version: 110000 metrics: - datname: { usage: LABEL ,description: "Database name of this default partition" } - relname: { usage: LABEL ,description: "Schema qualified default partition relation name" } - tuples: { usage: GAUGE ,description: "Number of tuples in this default partition" } #==============================================================# # 0810 pg_table_size #==============================================================# pg_table_size: desc: PostgreSQL table size metrics, quite slow query: |- SELECT CURRENT_CATALOG AS datname, nsp.nspname || '.' || rel.relname AS relname, pg_total_relation_size(rel.oid) AS bytes, pg_relation_size(rel.oid) AS relsize, pg_indexes_size(rel.oid) AS indexsize, pg_total_relation_size(reltoastrelid) AS toastsize FROM pg_namespace nsp JOIN pg_class rel ON nsp.oid = rel.relnamespace WHERE nspname <> ALL(ARRAY['pg_catalog', 'information_schema']) AND rel.relkind = 'r' ORDER BY 3 DESC NULLS LAST LIMIT 256; ttl: 300 timeout: 2 min_version: 100000 metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Schema qualified table name" } - bytes: { usage: GAUGE ,default: 0 ,description: "Total bytes of this table (including toast, index, toast index)" } - relsize: { usage: GAUGE ,default: 0 ,description: "Bytes of this table itself (main, vm, fsm)" } - indexsize: { usage: GAUGE ,default: 0 ,description: "Bytes of all related indexes of this table" } - toastsize: { usage: GAUGE ,default: 0 ,description: "Bytes of toast tables of this table" } #==============================================================# # 0820 pg_table_bloat #==============================================================# # pg_table_bloat require auxiliary view to work. Disable it or create auxiliary view before use: pg_table_bloat: name: pg_table_bloat desc: PostgreSQL table bloat metrics, require auxiliary view pg_table_bloat to work query: SELECT datname, nspname || '.' || relname AS relname, size, ratio FROM pg_table_bloat ORDER BY size DESC LIMIT 64; ttl: 300 timeout: 2 min_version: 090400 skip: true metrics: - datname: { usage: LABEL ,description: "Database name of this table" } - relname: { usage: LABEL ,description: "Schema qualified name of this table" } - size: { usage: GAUGE ,description: "Total bytes of this table" } - ratio: { usage: GAUGE ,description: "Estimated bloat ratio of this table from 0 to 1" } #==============================================================# # 0830 pg_index_bloat #==============================================================# # pg_index_bloat require auxiliary view to work. Disable it or create auxiliary view before use: pg_index_bloat: name: pg_index_bloat desc: PostgreSQL index bloat metrics (btree only), require pg_index_bloat query: SELECT datname, nspname || '.' || relname AS relname, size, ratio FROM pg_index_bloat ORDER BY size DESC LIMIT 64; ttl: 300 timeout: 2 min_version: 090400 skip: true metrics: - datname: { usage: LABEL ,description: "Database name of this index" } - relname: { usage: LABEL ,description: "Schema qualified index name" } - size: { usage: GAUGE ,description: "Total bytes of this index" } - ratio: { usage: GAUGE ,description: "Estimated bloat ratio of this index, 0~1" } #==============================================================# # 0910 pgbouncer_list #==============================================================# # http://www.pgbouncer.org/usage.html#show-lists pgbouncer_list: name: pgbouncer_list desc: Pgbouncer entry list query: SHOW LISTS; ttl: 10 min_version: 10800 fatal: true tags: [ pgbouncer ] metrics: - list: { usage: LABEL ,description: "Pgbouncer internal list name" } - items: { usage: GAUGE ,description: "Number of corresponding pgbouncer object" } #==============================================================# # 0920 pgbouncer_database #==============================================================# # http://www.pgbouncer.org/usage.html#show-databases pgbouncer_database_124: name: pgbouncer_database desc: Pgbouncer database stats (since 1.24) query: SHOW DATABASES; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool_size: { usage: GAUGE ,rename: reserve_pool ,description: "Maximum number of additional connections for this database" } - server_lifetime: { usage: GAUGE ,description: "The maximum lifetime of a server connection for this database" } - pool_mode: { usage: DISCARD } - load_balance_hosts: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - max_client_connections: { usage: GAUGE ,description: "Maximum number of allowed client connections for this pgbouncer instance" } - current_client_connections: { usage: GAUGE ,description: "Current number of client connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_123: name: pgbouncer_database desc: Pgbouncer database stats 1.23 query: SHOW DATABASES; ttl: 10 min_version: 12300 max_version: 12400 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - server_lifetime: { usage: GAUGE ,description: "The maximum lifetime of a server connection for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_116: name: pgbouncer_database desc: Pgbouncer database stats (1.16-1.22) query: SHOW DATABASES; ttl: 10 min_version: 11600 max_version: 12300 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - min_pool_size: { usage: GAUGE ,description: "Minimum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } pgbouncer_database_108: name: pgbouncer_database desc: Pgbouncer database stats (1.08-1.15) query: SHOW DATABASES; ttl: 10 min_version: 10800 max_version: 11600 tags: [ pgbouncer ] metrics: - name: { usage: LABEL ,rename: datname ,description: "Name of configured database entry" } - host: { usage: LABEL ,description: "Host that pgbouncer will connects to" } - port: { usage: LABEL ,description: "Port that pgbouncer will connects to" } - database: { usage: LABEL ,rename: real_datname ,description: "The real database name pgbouncer connects to" } - force_user: { usage: DISCARD } - pool_size: { usage: GAUGE ,description: "Maximum number of server connections" } - reserve_pool: { usage: GAUGE ,description: "Maximum number of additional connections for this database" } - pool_mode: { usage: DISCARD } - max_connections: { usage: GAUGE ,description: "Maximum number of allowed connections for this database" } - current_connections: { usage: GAUGE ,description: "Current number of connections for this database" } - paused: { usage: GAUGE ,description: "True(1) if this database is currently paused, else 0" } - disabled: { usage: GAUGE ,description: "True(1) if this database is currently disabled, else 0" } #==============================================================# # 0930 pgbouncer_stat #==============================================================# # http://www.pgbouncer.org/usage.html#show-stats pgbouncer_stat_124: name: pgbouncer_stat desc: Pgbouncer stats per database (since 1.24) query: SHOW STATS; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_server_assignment_count: { usage: COUNTER ,description: "Total times a server was assigned to a client" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - total_client_parse_count: { usage: COUNTER ,description: "Total number of prepared statements created by clients" } - total_server_parse_count: { usage: COUNTER ,description: "Total number of prepared statements created on a server." } - total_bind_count: { usage: COUNTER ,description: "Total number of prepared statements readied for execution by clients and forwarded to postgres" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_server_assignment_count: { usage: GAUGE ,description: "Average number of times a server as assigned to a client per second in the last stat period." } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } - avg_client_parse_count: { usage: GAUGE ,description: "Average number of prepared statements created by clients" } - avg_server_parse_count: { usage: GAUGE ,description: "Average number of prepared statements created on a server." } - avg_bind_count: { usage: GAUGE ,description: "Average number of prepared statements readied for execution by clients and forwarded to postgres" } pgbouncer_stat_123: name: pgbouncer_stat desc: Pgbouncer stats per database (1.23) query: SHOW STATS; ttl: 10 min_version: 12300 max_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_server_assignment_count: { usage: COUNTER ,description: "Total times a server was assigned to a client" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_server_assignment_count: { usage: GAUGE ,description: "Average number of times a server as assigned to a client per second in the last stat period." } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } pgbouncer_stat_108: name: pgbouncer_stat desc: Pgbouncer stats per database (1.08 - 1.22) query: SHOW STATS; ttl: 10 min_version: 10800 max_version: 12300 tags: [ pgbouncer ] metrics: - database: { usage: LABEL ,rename: datname ,description: "Name of database" } - total_xact_count: { usage: COUNTER ,description: "Total number of SQL transactions pooled by pgbouncer" } - total_query_count: { usage: COUNTER ,description: "Total number of SQL queries pooled by pgbouncer" } - total_received: { usage: COUNTER ,description: "Total volume in bytes of network traffic received by pgbouncer" } - total_sent: { usage: COUNTER ,description: "Total volume in bytes of network traffic sent by pgbouncer" } - total_xact_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when in a transaction" } - total_query_time: { usage: COUNTER ,scale: 1e-6 ,description: "Total number of seconds spent when executing queries" } - total_wait_time: { usage: COUNTER ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds" } - avg_xact_count: { usage: GAUGE ,description: "Average transactions per second in last stat period" } - avg_query_count: { usage: GAUGE ,description: "Average queries per second in last stat period" } - avg_recv: { usage: GAUGE ,description: "Average received (from clients) bytes per second" } - avg_sent: { usage: GAUGE ,description: "Average sent (to clients) bytes per second" } - avg_xact_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average transaction duration, in seconds" } - avg_query_time: { usage: GAUGE ,scale: 1e-6 ,description: "Average query duration, in seconds" } - avg_wait_time: { usage: GAUGE ,scale: 1e-6 ,description: "Time spent by clients waiting for a server, in seconds (average per second)." } #==============================================================# # 0940 pgbouncer_pool #==============================================================# # http://www.pgbouncer.org/usage.html#show-pools pgbouncer_pool_124: name: pgbouncer_pool desc: Pgbouncer pool stats (1.24+) query: SHOW POOLS; ttl: 10 min_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_active_cancel_req: { usage: GAUGE, rename: active_cancel_clients, description: "Client connections that have forwarded query cancellations to the server and are waiting for the server response." } - cl_waiting_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_active_cancel: { usage: GAUGE, rename: active_cancel_servers, description: "Server connections that are currently forwarding a cancel request" } - sv_being_canceled: { usage: GAUGE, rename: cancel_servers, description: "cancel requests have completed that were sent to cancel a query on this server" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } - load_balance_hosts: { usage: LABEL, description: "The load_balance_hosts in use" } pgbouncer_pool_118: name: pgbouncer_pool desc: Pgbouncer pool stats (1.18-1.23) query: SHOW POOLS; ttl: 10 min_version: 11800 max_version: 12400 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_active_cancel_req: { usage: GAUGE, rename: active_cancel_clients, description: "Client connections that have forwarded query cancellations to the server and are waiting for the server response." } - cl_waiting_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_active_cancel: { usage: GAUGE, rename: active_cancel_servers, description: "Server connections that are currently forwarding a cancel request" } - sv_being_canceled: { usage: GAUGE, rename: cancel_servers, description: "cancel requests have completed that were sent to cancel a query on this server" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pgbouncer_pool_116: name: pgbouncer_pool desc: Pgbouncer pool stats (1.16-1.17) query: SHOW POOLS; ttl: 10 min_version: 11600 max_version: 11800 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - cl_cancel_req: { usage: GAUGE, rename: cancel_clients, description: "Client connections that have not forwarded query cancellations to the server yet." } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } pgbouncer_pool_108: name: pgbouncer_pool desc: Pgbouncer pool stats (1.08-1.15) query: SHOW POOLS; ttl: 10 min_version: 10800 max_version: 11600 tags: [ pgbouncer ] metrics: - database: { usage: LABEL, rename: datname, description: "Database name of this pool" } - user: { usage: LABEL, description: "User name of this pool" } - cl_active: { usage: GAUGE, rename: active_clients, description: "Client connections that are linked to server connection and can process queries" } - cl_waiting: { usage: GAUGE, rename: waiting_clients, description: "Client connections that have sent queries but have not yet got a server connection" } - sv_active: { usage: GAUGE, rename: active_servers, description: "Server connections that are linked to a client" } - sv_idle: { usage: GAUGE, rename: idle_servers, description: "Server connections that are unused and immediately usable for client queries" } - sv_used: { usage: GAUGE, rename: used_servers, description: "Server connections that have been idle for more than server_check_delay (means have to run check query)" } - sv_tested: { usage: GAUGE, rename: tested_servers, description: "Server connections that are currently running reset or check query" } - sv_login: { usage: GAUGE, rename: login_servers, description: "Server connections currently in the process of logging in" } - maxwait: { usage: GAUGE, description: "How long the first(oldest) client in the queue has waited, in seconds, key metric" } - maxwait_us: { usage: GAUGE, description: "Microsecond part of the maximum waiting time." } - pool_mode: { usage: LABEL, description: "Pooling mode in use" } #==============================================================# # 1000 pg_wait_event #==============================================================# pg_wait_event: name: pg_wait_event desc: PostgreSQL wait event sampling based on pg_wait_sampling extension query: SELECT coalesce(event_type, 'Running') AS etype, coalesce(event, 'Running') AS event, sum(count) AS count FROM pg_wait_sampling_profile GROUP BY 1,2; ttl: 10 min_version: 100000 tags: [ cluster, "extension:pg_wait_sampling" ] metrics: - etype: { usage: "LABEL" ,description: "wait event type" } - event: { usage: "LABEL" ,description: "wait event name" } - count: { usage: "COUNTER" ,description: "Total count of wait events sampled" } pg_wait_event_1s: name: pg_wait_event_1s desc: PostgreSQL wait event sampling based on pg_wait_sampling extension query: SELECT coalesce(event_type, 'Running') AS etype, coalesce(event, 'Running') AS event, count(*) FROM pg_wait_sampling_history WHERE ts BETWEEN now() - '1s'::INTERVAL AND now() GROUP BY 1,2; ttl: 10 min_version: 100000 tags: [ cluster, "extension:pg_wait_sampling" ] metrics: - etype: { usage: "LABEL" ,description: "wait event type" } - event: { usage: "LABEL" ,description: "wait event name" } - count: { usage: "GAUGE" ,description: "Number of wait events in last second" } #==============================================================# # 1800 pg_tsdb_hypertable #==============================================================# # this collector reqires timescaledb extension to be installed pg_tsdb_hypertable: name: pg_tsdb_hypertable desc: TimescaleDB hypertable overview query: |- SELECT current_database() AS datname, format('%I.%I', hypertable_schema, hypertable_name) AS relname, num_dimensions AS dimensions, num_chunks AS chunks, compression_enabled::BOOLEAN::int AS compressed, hypertable_size(format('"%I"."%I"', hypertable_schema, hypertable_name)::RegClass) AS bytes FROM timescaledb_information.hypertables; ttl: 60 timeout: 2 min_version: 100000 skip: true tags: [ "extension:timescaledb", "schema:timescaledb_information" ] metrics: - datname: { usage: LABEL ,description: "database name" } - relname: { usage: LABEL ,description: "Hypertable relation name" } - dimensions: { usage: GAUGE ,description: "Number of partitioning dimensions" } - chunks: { usage: GAUGE ,description: "Total chunks of this hypertable" } - compressed: { usage: GAUGE ,description: "1 if compression enabled" } - bytes: { usage: GAUGE ,description: "Total size of hypertable in bytes" } #==============================================================# # 1900 pg_citus_node #==============================================================# # https://docs.citusdata.com/en/latest/develop/api_metadata.html#worker-node-table pg_citus_node: name: pg_citus_node desc: Citus worker coordinator node inventory query: |- SELECT CONCAT(nodename, ':', nodeport) AS node, current_database() AS datname, nodeid AS id, groupid AS group, hasmetadata::BOOLEAN::INT AS has_meta, isactive::BOOLEAN::INT AS is_active, metadatasynced::BOOLEAN::INT AS meta_synced, shouldhaveshards::BOOLEAN::INT AS have_shards FROM pg_dist_node; ttl: 60 min_version: 100000 tags: [ "extension:citus" ] metrics: - node: { usage: LABEL ,description: "nodename:port of the PostgreSQL instance" } - datname: { usage: LABEL ,description: "database name" } - id: { usage: GAUGE ,description: "auto‑generated node identifier" } - group: { usage: GAUGE ,description: "replication group id (primary + secondaries)" } - has_meta: { usage: GAUGE ,description: "1 = internal use flag set" } - is_active: { usage: GAUGE ,description: "1 = node currently accepts shards" } - meta_synced: { usage: GAUGE ,description: "1 = metadata fully synced to node" } - have_shards: { usage: GAUGE ,description: "1 = rebalancer may place shards here" } #==============================================================# # 2000 heartbeat #==============================================================# # this is a example of application monitoring and predicate queries pg_heartbeat: name: pg_heartbeat desc: monitoring heartbeat in monitor.heartbeat table predicate_queries: - name: if heartbeat table exists predicate_query: | SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'monitor' AND table_name = 'heartbeat'); query: |- SELECT id AS cluster_name, extract(EPOCH FROM ts) AS ts, lsn, txid FROM monitor.heartbeat; ttl: 10 min_version: 090100 tags: [ "dbname:postgres", "schema:monitor" ] skip: true metrics: - cluster_name: { usage: LABEL ,description: "cluster_name param of this database cluster" } - ts: { usage: GAUGE ,description: "unix timestamp of the heartbeat" } - lsn: { usage: COUNTER ,description: "lsn of the heartbeat" } - txid: { usage: GAUGE ,description: "txid of the heartbeat" } pg_exporter-1.4.1/static/000077500000000000000000000000001523237665500153605ustar00rootroot00000000000000pg_exporter-1.4.1/static/favicon.ico000066400000000000000000000360561523237665500175130ustar00rootroot00000000000000 h6  (ž00 h&Æ(  °„O¬‚Nh¬ƒOŽŠnJŽ™Æõk™Ìÿ¢‹] ª‚N“ª€Nùª€Mÿª€Mÿ”pEÿ�µáÿ™Æ÷ùšÇú’‹¹ÿ @@@ ¨€MĪ€Mÿª€Mÿª€Mÿª€Mÿ˜rEÿo|ˆÿˆ‘ÿ‹³Þÿ™ÇøÃ•¿ÿ ’½ë›†oQÿª€Mÿª€Mÿª€Mÿ�lAÿjO.ÿ yIÿ–qEÿ}rbÿ˜Å÷ÿšÈúš›Çù)˜Æøûz”¯ÿ„d;ÿ�lAÿ€`9ÿmR/ÿ‰g=ÿžwGÿcqÿpW8ÿбÝÿ˜ÆøûŸÌù(›Éûz˜Å÷ÿzumÿ¦}Kÿ~_8ÿ§~Lÿ¥}Kÿ©Mÿ“nBÿy“°ÿg}—ÿs�¯ÿ–ÃõÿšÆùy™Çù£„¥Éÿ–sHÿª€Mÿ�j?ÿƒqXÿ‚qXÿ¢zIÿ‡f>ÿ€£Éÿx‘®ÿ«Êîÿv’±ÿšÈù¡šÆù¦~zqÿª€Mÿ©Mÿv^Cÿrncÿcc[ÿ—sFÿƒc;ÿz“®ÿz˜¹ÿ”­Êÿu�­ÿ˜Åù¤›Çù„}{vÿ§~Lÿˆf<ÿ¦~Lÿlh]ÿli^ÿ§}Lÿ¦}Kÿz“®ÿ˜Å÷ÿ†­Öÿ•ÁòÿšÈù�žÇú7ˆ¬ÔþvbHÿ€`8ÿiN-ÿfL*ÿS>!ÿE3ÿ5'ÿOZcÿ—Äöÿ˜Åøÿ˜Æøþ˜Éú4ÿÿÿ™Æø´y™¼ÿKV`ÿB0ÿP;ÿN:ÿP;ÿLB3ÿg}•ÿ—Ä÷ÿ˜Åøÿ𯸰ÿÿÿ™Ìÿ™ÆøÜ�¸æÿ(ÿ&ÿ&ÿ&ÿR^kÿ˜Åøÿ˜Åøÿ™ÅùÙ›Èÿ—Æö™Çø·i€˜þ+%ÿ% ÿ42,ÿ‡¬Öÿ™ÆøþšÇùµ™ÂõÿÿÿœÇû;–À·â­˜Åöª›Ç÷„›Èú8ÿÿÿÿÿ( @ €€€¿€@­€R¨€K,¨€K,d[IŽÆÿ €ÿÿÿª€U¬‚Qb«‚O±«‚Oå«‚Nø«ƒOú¬‚OúfVDø—Ãóé›Çù·›Èúf”Éòÿÿ¢‹] «�Owª�Nçª�Mþª€Mÿª€Mÿ©€Mÿª€Mÿ©Mÿw[;ÿбÛÿ˜Å÷ÿ™Å÷þ™Èùç˜Çùw¢Ñÿ ¬‚S+«‚O̪�Mþª€Mÿª€Mÿª€MÿªMÿª€Mÿª€Mÿª€Mÿˆg>ÿzš¼ÿ—Å÷ÿ˜Å÷ÿ˜Å÷ÿ™Æøþ›ÇúʘÈù*ÿ©€J>«�Né©Mÿª€Mÿª€Nÿª€Mÿª€Mÿª€MÿªMÿª€Mÿª€MÿŽkAÿt’´ÿ‘»éÿ‰±Ûÿ“¾îÿ˜Å÷ÿ˜Å÷ÿ™Çøè–Äû=ÿ5::0¢{J몀Mÿª€Nÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€MÿªMÿ~^8ÿLNKÿkU:ÿy];ÿeT?ÿf}•ÿ—Äöÿ˜Åøÿ™Æøê›Èÿ.�¿ï†ªÒÓsZ<ÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Nÿª€Mÿª€Mÿ¤{Jÿ€`9ÿQ="ÿ¢zJÿª€Mÿª€Mÿª€MÿƒfAÿm‰¦ÿ˜Åøÿ˜Å÷ÿ›ÇúÓ�¿ïÿÿÿ›ÉûŠ™Æøþ\grÿ yJÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿ‘mAÿbI*ÿŠh=ÿK8ÿ“oBÿ¡yIÿŠg=ÿz];ÿ¥|Kÿ^\Vÿ˜Ä÷ÿ˜Å÷ÿ™ÆøþœËûˆÿÿÿ›É÷!šÇùò˜Å÷ÿޏäÿZRFÿ¨Lÿª€Mÿª€Mÿ©Mÿ–qCÿqU2ÿO; ÿ¨LÿhM-ÿ–qDÿª€Mÿª€MÿWODÿmˆ¥ÿ~`<ÿoX:ÿ~¡Çÿ˜Åøÿ˜ÅøÿšÇúñ—Ç÷ ÿÿÿœÊû�™Æøþ˜Åøÿ˜Å÷ÿg�›ÿT?%ÿlP.ÿ‘mAÿ\E'ÿtW4ÿ…d:ÿQÿª€Mÿª€Mÿª€Mÿª€MÿoR0ÿª€Mÿ¤{Kÿ£{Kÿª€Mÿ¨Lÿ¦}Kÿª€Mÿ˜rFÿcxŽÿ˜Ä÷ÿ~ŸÄÿo€“ÿ²Ôúÿ–¯ÊÿRaoÿ˜Ä÷ÿ™ÇùýšÆû:ÿÿÿ›ÊüR™Åøþ–ÂóÿTXYÿ yIÿª€Mÿª€Mÿª€Mÿª€MÿsU0ÿscNÿMfzÿKgyÿobMÿ©€Mÿ‘l@ÿuX4ÿeL/ÿmŠªÿ˜Å÷ÿu’´ÿ~”¬ÿ²Óûÿ³Ôúÿ`n|ÿ�¶âÿ™Æøþ›ËüOÿÿÿÿÿ�ËüS™Æøþw˜¸ÿ‚c=ÿª€Mÿª€Mÿª€Mÿª€Mÿ¤{JÿR?)ÿUq‡ÿ‡fBÿrV7ÿTq…ÿ�kBÿª€Mÿ˜rEÿ›uGÿ\jzÿ—Å÷ÿ�¹æÿVamÿ±Òùÿ³Ôûÿpƒ–ÿ…ªÒÿ™Åøþ›ÊüRÿÿÿ›ÈüJ™Æøþ`s…ÿ yIÿª€Mÿ«€Mÿª€Mÿ¥}Kÿ—qEÿLNRÿ_x„ÿ�j?ÿfL+ÿ`x„ÿ{_=ÿª€Mÿ_G)ÿ|\6ÿ[WOÿ˜Å÷ÿ˜Å÷ÿi€œÿco~ÿ‰žµÿJR[ÿ”Àðÿ™Æøþ—Å÷B›Çù)šÈùúbyŽÿžwIÿª€Mÿª€Mÿª€MÿsV2ÿ§~LÿŸxHÿNesÿpneÿroeÿOhxÿ�vGÿªMÿ§}Kÿ©Lÿ[VOÿ˜Ä÷ÿ˜Å÷ÿ—Ä÷ÿ€£Èÿj†¤ÿ�µàÿ˜Å÷ÿ›ÈúùœÆø$Žªÿ ™Çøä€£ÈÿsY9ÿª€Mÿ yIÿyZ5ÿ‰h=ÿª€Mÿª€Mÿ�kBÿcaYÿcaYÿŒjBÿª€Mÿª€Mÿª€Mÿ�wHÿ\m}ÿ˜Å÷ÿ˜Åøÿ˜Å÷ÿ˜Å÷ÿ˜Å÷ÿ˜Åøÿ˜ÅøÿšÈùß¶Ûÿÿÿÿ›Èúž—ÄöÿYdpÿ�kAÿª€Mÿª€Mÿ©Mÿ—qDÿ©Mÿ§~Kÿ—rCÿvX4ÿ\D&ÿM8ÿA0ÿ?/ÿ:*ÿTbqÿ‹³Þÿ˜Å÷ÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Æøÿ˜ÅøÿšÉú—ÿÿÿ›Éû=™Æùû•ÁñÿS`pÿN;"ÿaI(ÿK8ÿ- ÿ8)ÿ0#ÿ) ÿ9*ÿB1ÿE3ÿA/ÿ4&ÿ'ÿ' ÿ552ÿ•Áñÿ˜Å÷ÿ˜Åøÿ˜Åøÿ˜Åøÿ™Çùú™Çú7€¿¿šÈùµ˜Å÷ÿ‰°Ûÿ01-ÿ$ÿ$ ÿ1#ÿH5ÿ_G%ÿmQ,ÿnR-ÿnR,ÿnR,ÿoS-ÿlP,ÿ, ÿ)ÿIU`ÿ–Ãôÿ˜Åøÿ˜Åøÿ˜Åøÿ˜ÅøÿšÇù®ªªÿšÊù+šÇúð˜Åøÿ”¿ïÿ‡®Øÿ]sŠÿG5ÿF4ÿ>-ÿ5&ÿ/" ÿ- ÿ.! ÿ3&ÿ?.ÿZizÿ’¼ëÿ—Äöÿ˜Å÷ÿ˜Åøÿ˜Åøÿ˜Æøÿ™ÇùíšÉø&ÿ˜Èúa˜Æùû˜Å÷ÿ˜Å÷ÿx˜»ÿ& ÿ&ÿ&ÿ%ÿ&ÿ&ÿ&ÿ&ÿ' ÿm‡¦ÿ˜Å÷ÿ˜Åøÿ˜Å÷ÿ˜Åøÿ˜Åøÿ™Æùú›ÉùYÿ€ÿÿ›Éûz™Åøü˜Å÷ÿ“¾îÿ.*&ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ& ÿ( ÿŒµàÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Æùû—Çúq€€ÿ€ÿÿ™ÉýgšÇùò˜Åøÿnˆ¤ÿ'ÿ&ÿ%ÿ%ÿ&ÿ&ÿ& ÿVfvÿ˜Åøÿ˜Å÷ÿ™Åøÿ˜Åøÿ™ÇùñšÊú`€€ÿÿÿšÊú0šÈú»™Æùûv”µÿ;?Bÿ%ÿ$ÿ#ÿ,( ÿYj|ÿ•Áñÿ˜Åøÿ˜Å÷ÿ™ÇùûšÈù¹™Ìù-ÿ¿¿ÿœÊûCšÈú§™Ç÷ë�¹æü†¬Ôþ‰°Ûý”Àïý™ÆøþšÈùúšÇùã™ÈúžšÆû?€¿ÿÿÿÿ�Äÿ ›Èú3›ÍüW¡ÏüdžÍüa˜ÇøM›Çù)Žªã ÿÿÿÿÿÿÿÿÿÿÿ(0` €€ªªU³€M ªˆU¥‡K¥‡KˆwDMMM €¿ÿ€ÿÿÿ€€€³€M ®€Q,®�NU­‚Nv«‚P�®ƒOž®ƒP¦®ƒP¦˜sHŸNSU�™Ä÷zœÉüZ—Æú1•¿ê €ÿÿÿÿÿÿÿÿªwU«~NU¬‚O¯¬‚OÛ«�Oíª�Mø©Ný«‚Nüª�Nýª€Ný¡yJüH?7ý’¼ëùšÆùï›ÉúÞšÇùµ›ÉüY�¿ÿÿÿÿ€€€³ŒM«~NU¬�OÅ«‚Nøª€Ný«�Mþª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿ©Mÿ¨~MÿRA.ÿ…ªÑÿ˜Ä÷ÿ™Æøþ™Å÷þšÇùø›ÉûÆ™ÆüU’Âó€€ÿ¿€@§€P@¬�O¨ª�Mñª�Mþª€Mÿª€Mÿª€Mÿª€Mÿ©Mÿ©Mÿª€Mÿª€Mÿ©€Mÿ©€MÿdL0ÿs‘®ÿ˜Å÷ÿ˜Å÷ÿ˜Å÷ÿ˜Å÷ÿ™Æøþ™ÇùñšÈù§˜Å÷>€¿¿Ÿ€@­ƒRg¬‚Pàª�Mûª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Nÿª€Mÿ©Mÿª€Mÿª€MÿwY6ÿbwŽÿ˜Å÷ÿ—Åøÿ˜Åøÿ˜Å÷ÿ˜Å÷ÿ˜Å÷ÿ™ÆøûšÇùà�Êüe€¿ß™wU©Mz«‚Oð©Mÿª€MÿªMÿª€Nÿª€Mÿª€Mÿª€Mÿª€MÿªLÿªMÿªMÿª€Mÿª€Mÿª€Mÿ}^:ÿZpˆÿ—Äöÿ”Àñÿ’¾íÿ”Àðÿ—Äõÿ™Å÷ÿ˜Åøÿ˜Å÷ÿ›Èùï˜Äùyˆ»îFFF ©~K}ª€Míª€MÿªMÿª€Mÿª€Mÿª€Mÿª€MÿªMÿª€Mÿª€Mÿª€MÿªMÿª€Mÿª€Mÿª€Mÿª€MÿtV4ÿYk{ÿm‡£ÿao~ÿbksÿ`mzÿg�œÿбÛÿ—Äöÿ˜Å÷ÿ˜Å÷ÿ˜Æùì˜Åù|ªÆÿ f™™+24q–rDò©€Mÿª€Mÿª€Nÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€MÿªLÿ©Mÿ[D'ÿ82(ÿpZ>ÿ‘oEÿ™tGÿ”pEÿx_@ÿEIMÿ| Æÿ˜Ä÷ÿ˜Åøÿ˜Å÷ÿ™Çùñ›Êýn™™ÿªªÿ˜ÈøJnˆ¦ä_K2ÿ¨~Lÿª€NÿªMÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Nÿª€Mÿª€Mÿª€Mÿª€Mÿ§}Kÿ�vGÿ|]7ÿE5ÿ•pCÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿ�mEÿQWZÿŒ³ßÿ˜Åøÿ˜Å÷ÿ˜Å÷ÿšÈùäšÇüIªªÿ›¿öœÈù¶“½ìüMRRÿ�mCÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿ©€Mÿ”oBÿjO-ÿcK*ÿWA$ÿV@#ÿ˜sDÿª€Mÿ¢zIÿ¨~Lÿ©€Mÿ©Mÿ¨~LÿoW:ÿoˆ ÿ˜Å÷ÿ˜Å÷ÿ˜Å÷ÿ™Æøü›Èûµ—ÆöÿÿÿšÇør™Çúö˜Å÷ÿw—·ÿbP=ÿ¥|Lÿª€Mÿª€Mÿª€Mÿ«€Mÿª€MÿªMÿª€Mÿª€Mÿª€Mÿ›tFÿ\D'ÿ„c:ÿœuGÿwZ4ÿ@0ÿ‚c:ÿª€MÿœuFÿmQ.ÿnT5ÿ\G.ÿžwHÿ�mCÿQ[aÿ—Äöÿ˜Å÷ÿ˜Å÷ÿ™Æøÿ™Çøö™Êúnÿÿÿÿÿÿ”Å÷šÉúß™Æøþ˜Å÷ÿ’¾íÿYhvÿ~a@ÿ©Mÿª€Mÿª€Mÿª€Mÿª€Mÿ©Mÿ¥|Kÿ�vGÿŒi?ÿS>"ÿy[5ÿ¨MÿŠg=ÿXB&ÿ›uFÿª€Mÿª€Mÿ©MÿlT7ÿUgzÿ\o„ÿbL1ÿ�vHÿG@4ÿ�¥Êÿ˜Å÷ÿ˜Åøÿ™Æøÿ™Æøþ›ÉúÞ™Ãöÿÿÿ¿¿ÿ›ÉùšÇøü˜Åøÿ˜Åøÿ˜Åøÿ†­ÖÿNTWÿ`:ÿžvGÿ¦}Kÿª€Mÿ¡yIÿxZ5ÿcJ+ÿaI)ÿYC%ÿ0$ÿ…d;ÿŸxHÿW@%ÿ�l@ÿªMÿ¨Lÿ©Mÿª€MÿZN>ÿ|žÁÿQbsÿsY8ÿ§~LÿoU5ÿM_qÿ—Ãõÿ˜Å÷ÿ˜Åøÿ˜Å÷ÿ™ÇùûšÊû|¿¿ÿ›Èÿ›ÊûÍ™Æøþ˜Å÷ÿ™Æøÿ˜Åøÿ�ºçÿKZiÿ8*ÿK8 ÿM9ÿ‚b9ÿ`G(ÿhM,ÿ’nBÿ�wGÿ�a8ÿ^E&ÿXB$ÿO; ÿxZ4ÿ¨~Lÿª€Mÿ yHÿ|\5ÿwY3ÿA;6ÿƒ¨Ðÿb~˜ÿE:+ÿaI,ÿ=6,ÿv—·ÿ˜Å÷ÿ˜Å÷ÿ˜Åøÿ˜Å÷ÿ˜ÆøþœÉüË¡Æÿÿÿÿ›ËüJ›Çúê˜Åøÿ˜Åøÿ˜Åøÿ“¾íÿXk~ÿdR8ÿ¡yIÿ¦}Kÿ�l@ÿC2ÿZD&ÿ£|Jÿª€Mÿª€Mÿ�l@ÿ‹h=ÿ¥}Kÿ¤{Jÿª€MÿªMÿªMÿª€Mÿ¡yHÿ’mAÿOE;ÿ‚§Ïÿ–Ãôÿz™ºÿMU^ÿ?ENÿ”Àðÿ—Ä÷ÿ˜Å÷ÿ˜Å÷ÿ˜Å÷ÿ˜ÅøÿšÇûéšÇøIÿÿÿ™Ìÿ›ÈûxšÇù÷˜Å÷ÿ˜Åøÿ˜Åøÿ~ Ãÿ^P?ÿ¢{Kÿª€Mÿª€Mÿª€Mÿ•qDÿdK+ÿŸxHÿª€Mÿª€Mÿ¨~Lÿ¢zIÿª€Mÿª€Mÿª€MÿªMÿ¦}Kÿ¢zIÿ§~Lÿ¢zJÿNF=ÿ„©Ðÿ˜Åøÿ˜Ä÷ÿ‚¦Íÿ124ÿ_v�ÿs�²ÿ�¸åÿ˜Å÷ÿ˜Å÷ÿ˜Å÷ÿ™Çùö›Éûu¿ÿÿ’Èÿ›ÉúœšÆùü˜Å÷ÿ˜Åøÿ˜Å÷ÿ_n~ÿ`=ÿª€Mÿª€Mÿª€MÿªMÿ¨~LÿoS0ÿ�l?ÿª€Mÿª€Mÿª€Mÿª€Nÿª€Mÿª€Mÿª€Nÿª€Mÿ¨~LÿŠg;ÿoR.ÿ]E'ÿ:::ÿвÜÿ˜Åøÿ—Äöÿr�ªÿ236ÿZfrÿNS^ÿ@HSÿ‰±Úÿ˜Å÷ÿ˜Åøÿ™ÅøüšÉú˜�Äë žÂÿ™Çù·™Æøþ˜Åøÿ˜Åøÿ�¹æÿGGDÿ›uHÿª€MÿªMÿª€Mÿª€Mÿª€Mÿ~_9ÿ�`9ÿ©Mÿ©€Mÿ§~Mÿ§~Lÿ©Mÿª€Mÿª€Mÿ¨Lÿ©€Mÿª€Nÿ§~Lÿ™sFÿBFFÿ’¼ìÿ˜Å÷ÿ‘»êÿN\lÿ‰ ¹ÿ²Óúÿ®Îñÿp~Œÿ?HPÿ�µàÿ˜Åøÿ™Çùý™Éù²”Éò•ÊÿšÆùɘÅ÷ÿ˜Å÷ÿ‘»éÿYjyÿiQ3ÿ¨Lÿª€Mÿª€Mÿª€Mÿª€Mÿª€MÿŠh=ÿvX2ÿ¤{Kÿ€cBÿk_QÿkaRÿ~cBÿ£{Kÿ©Mÿ�vFÿŒh=ÿ�a:ÿ{]7ÿ\F,ÿN`rÿ–Ãõÿ˜Å÷ÿ‹²ßÿNZhÿ¡¾ßÿ²Ôûÿ³Ôûÿ¯Îòÿ\gqÿg™ÿ—Ãõÿ˜ÅøÿšÆøÄ�Îÿÿÿÿ—Ï÷ šÈú͘Å÷ÿ—Ãõÿiƒ ÿ`P;ÿ¤|Kÿª€Mÿª€MÿªMÿª€Mÿ«€Mÿª€Mÿ�l?ÿpS.ÿn[CÿWuŒÿPu�ÿOt‹ÿVv‹ÿgWBÿ©Mÿ§~LÿœuFÿŠh>ÿ‡e<ÿwZ8ÿOatÿ—Ãõÿ˜Å÷ÿ�µâÿO[jÿž¼Üÿ²Óûÿ³Ôûÿ³ÔúÿާÁÿSewÿ“¾îÿ˜ÆøÿšÈúÌœÎÿÿÿÿÿÿÿ›É÷!›ÇùΙÅ÷ÿ�¹åÿX[\ÿ—rFÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Nÿ©Mÿ‹i?ÿZC(ÿH^pÿiryÿ‹i@ÿ€`;ÿelqÿGatÿ‡f>ÿª€Mÿª€Mÿ¨}Lÿ¡yIÿštGÿ@BHÿ‘»éÿ˜Å÷ÿ“¾ïÿTexÿ�–¬ÿ²Óùÿ²Óûÿ³ÔûÿšµÔÿTgwÿ‘»êÿ˜Å÷ÿšÈúÍŸÏ÷ ÿÿÿÿÿÿ—Ç÷ šÇú͘Å÷ÿ…ªÒÿ_TCÿ¤|Kÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿª€Mÿ£zJÿfZKÿ;?Fÿ^…¢ÿ�oRÿsW3ÿ@/ÿq^Eÿ`‡¥ÿ_K3ÿª€Mÿª€Mÿ�i?ÿV@$ÿœuFÿQG;ÿ~¡Åÿ˜Å÷ÿ˜Äöÿy˜»ÿEIRÿ–°Íÿ²Óøÿ²Óùÿ€“§ÿVhyÿ”¿ðÿ˜Å÷ÿšÆùÉ•Êÿÿ�Äõ™ÇøÃ˜Ä÷ÿ~žÀÿeN2ÿ§~Lÿª€Mÿª€Mÿ«€Mÿª€Mÿª€Mÿ�vGÿ¤{Jÿz`CÿD61ÿ^†£ÿ�nPÿŒi>ÿ^F(ÿygJÿ`Š©ÿ^K3ÿª€MÿªMÿ‡e<ÿB1ÿ›tEÿmX@ÿn�­ÿ˜Å÷ÿ—Å÷ÿ–Áñÿg}™ÿ98<ÿgt€ÿhu�ÿ:=Aÿu•¸ÿ—Ä÷ÿ™Å÷ÿ›ÇúºžÎÿ–ÃÿœÉûª™Çøý€¡ÆÿcP6ÿ¦~Lÿª€Mÿª€Mÿª€Nÿª€Mÿ¤|JÿkO,ÿ¤|Jÿª€Mÿ�l@ÿH^mÿaorÿŽqNÿ�rOÿaosÿKduÿŠf=ÿªMÿªMÿ¨~Lÿ£zIÿ©MÿlX?ÿn�­ÿ—Ä÷ÿ˜Å÷ÿ—Å÷ÿ—Äöÿ~¡ÄÿSfxÿO_rÿ{œ»ÿ—Ãõÿ˜Åøÿ™Çøý›Éú¤ªÌÿŽªã ›ÉùŠ˜Æøû‰¯Ùÿ\UIÿ¡yJÿª€Mÿ©€MÿŸwGÿ�l@ÿkP/ÿnS0ÿ©Mÿª€Mÿ¨~LÿlX?ÿNdtÿSm€ÿUmÿMeuÿkY?ÿ§~LÿªMÿª€Mÿª€Mÿª€Mÿ¨~LÿOE9ÿ¡Æÿ˜Å÷ÿ˜Å÷ÿ˜Åøÿ˜Å÷ÿ˜Å÷ÿ—Äöÿ—Äõÿ˜Å÷ÿ˜Å÷ÿ˜Å÷ÿ™Æùú™Çù…¶Ûÿªªÿ›Éúc™Çøò’½ìÿUalÿŠjAÿª€Mÿª€Mÿ xHÿ†d;ÿ‹h>ÿ£{Jÿª€Mÿª€Mÿª€Mÿ¥|KÿŠlFÿoXAÿpY@ÿŠkEÿ¤{Kÿª€Mÿ©€Mÿª€Mÿª€Mÿª€Mÿ‰i@ÿAHOÿ’½ìÿ™Æøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ™ÇùðžÊü\ªªª™Çú2›ÉúÞ˜Åöÿx™¼ÿKC:ÿ˜sEÿ¨Mÿª€Mÿª€Nÿ©MÿªLÿ¥|Kÿ¤|JÿªLÿ©Mÿ¦~Lÿ¡yIÿ™sEÿ�j?ÿ}^7ÿmP.ÿ\D$ÿP< ÿM: ÿXA#ÿ8(ÿ[m|ÿвÝÿ–Âóÿ˜Åøÿ˜Å÷ÿ˜Åøÿ˜Åøÿ˜Åøÿ™Æøÿ˜Åøÿ˜Æøÿ˜ÅøÿœÉûÚ—Åù,‰Äë ›Éûª™Æøþ–Âóÿl„¢ÿNE<ÿvZ7ÿ™sEÿ¢zIÿ�wGÿ†e<ÿ_G(ÿgM,ÿyZ6ÿrU2ÿbI)ÿO:ÿ9*ÿ+ ÿ" ÿ# ÿ& ÿ& ÿ' ÿ' ÿ' ÿ(ÿ<@Cÿn‹¨ÿ˜Äöÿ˜Åøÿ˜Å÷ÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ™Çùý›Éú¡ªÆÿ €€ÿ—ÆûGšÈúö˜Å÷ÿ–Ãôÿ{›¿ÿ964ÿ1%ÿ<-ÿ5(ÿ/"ÿ) ÿ+ ÿ- ÿ+ ÿ) ÿ2$ÿA0ÿO;ÿWA!ÿZB!ÿXA!ÿR<ÿB0ÿ.! ÿ&ÿ' ÿ' ÿ30+ÿ‘»èÿ˜Å÷ÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Æ÷ÿ˜ÅøÿšÈúô›Éû=ÿÿÿ€¿ßšÈû¬˜Æøý˜Åøÿ�¹éÿKXdÿ'ÿ& ÿ& ÿ& ÿ) ÿ.! ÿ9*ÿN:ÿcJ'ÿmQ,ÿoR-ÿnR,ÿnR,ÿnR,ÿnR-ÿoR,ÿnQ-ÿN9ÿ) ÿ' ÿ)"ÿ@INÿ”¿ïÿ˜Åøÿ™Åøÿ˜Åøÿ˜Åøÿ™Æøÿ™Æøÿ™Æøü›Éú¡ªÔÿÿ—Çû;šÇùܘÅ÷ÿ—Ä÷ÿ„ªÒÿ\q…ÿL[jÿKXgÿ0-)ÿG4ÿ^F$ÿfL)ÿdJ(ÿ^F$ÿW@!ÿS=ÿQ<ÿP:ÿQ;ÿS> ÿYB!ÿaH&ÿF4ÿNV^ÿYh}ÿe{”ÿˆ°Úÿ˜Å÷ÿ˜Å÷ÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ™ÆøþšÈùוÅõ5ÿªÆÿ ™Çù€™Çùö™Æøÿ˜Åøÿ–Âóÿ“½íÿ‘»êÿGTdÿ5(ÿ6'ÿ0#ÿ-! ÿ- ÿ+ ÿ) ÿ) ÿ) ÿ) ÿ* ÿ, ÿ." ÿ/%ÿ}�Àÿ–Áóÿ—Ãõÿ˜Åøÿ˜Åøÿ˜Å÷ÿ˜Åøÿ˜Æøÿ˜Åøÿ˜Æøÿ™ÇùôœÉýv¶Ûÿ™Ìÿ›Éûµ˜Æøý˜Å÷ÿ˜Å÷ÿ™Å÷ÿ˜Å÷ÿYo†ÿ'ÿ&ÿ&ÿ&ÿ%ÿ%ÿ%ÿ&ÿ&ÿ&ÿ&ÿ&ÿ' ÿ210ÿ†¬Öÿ˜Å÷ÿ˜Åøÿ˜Å÷ÿ˜Å÷ÿ˜Å÷ÿ˜Åøÿ™Æøÿ˜Å÷ÿ™Æùû�Ëû¬žÎÿÿÿÿ—Åù,šÇúÀ™Æøü˜Å÷ÿ˜Åøÿ˜Å÷ÿ}žÁÿ%ÿ%ÿ%ÿ&ÿ%ÿ%ÿ&ÿ'ÿ&ÿ&ÿ&ÿ& ÿ' ÿBHPÿŽ·åÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Åøÿ˜Å÷ÿ˜Åøÿ˜Åøÿ˜Æùû˜Æû·œÆÿ$ÿÿÿÿÿÿ›Èù.™ÈúÁ˜Æøý™Å÷ÿ™Åøÿ”ÀðÿaËgAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<xeXIfMM*>F‡iNHH  € €§ªÀ pHYs  šœ–iTXtXML:com.adobe.xmp 72 72 1 1024 1 1024 ±ò/&@IDATxí}€UÕµöº½Lf`¨2´¡wA�"–Xb[ÔÄåÅ$–hLLÌï`òb4&jb5¶`y¿�hŒ-*eh¤+Hï00Ãôrû½ÿ÷­}Î�1Ï2 ¢y?{æž²ËÚk¯µöÚ{¯]ŽÈqwœÇ)pœÇ)pœÇ)pœÇ)pœÇ)pœÇ)pœÇ)ðÿ ÿ› šJ¥2w†S ZåœôÅ]d«JS2}CJ䎔ÃáÀý¸ûÊS€ÌN¥æ¸R ºy?š/\Xæ&\½—•9�&ì/Ö¿µ˜3gºkzÁu™¼(ép”%ÿ•˜©Å¿,ˆ%ÜÅ1§·»Ëá,v¤…¨ÒÁd*é‡ÃéLIBÃc$�ÆáS‹ê^%ñä§;Záu&8&ÞVû?àR³”—»Êe‘Lž|GâßYKüÛ @Š5pÒ$'(ÿ§æýWaÄ•9<å”±‡{ÜÚ»«ËéÊv|".T\'~I°8…Ÿƒ™Ú�Ï$~9Š'$ŠÐ§&™LíGŒ-I®M¦b«œ±èÚÀ©?ÞÝV(RÐúþo( ÿ6�š3ÇU^°Á1eJYÜ&~tá}ÃNçY"žÓÁÛáÞ /S<ÃÈx\`d<‘dÛ� z`MM¥’ZfØKöÛW"Š„BoDty\nqyÀ[7üèOJ¤’‘JnLJj‰3{#ÜT¹4÷ì_¥µ›Š¹UL͘17A@_u÷•­]‹$é(3*>ôÚ]=%¼ŒºØíöseEbq‰Gbdx ‰ ¾¦œ`¸#þ;YJ­å¸#�Á¬ù:hň-Õ^¤Æt¸œ—Ç !£`$âij©•TbA2‘˜p4¼ê˜RÖĘX™N3¾Ò‚ð•e|•šßT‡Ûõ]qºÎñåfy �D¢äb EsŽÐÛUÏŸ2EM–D2©RA³À.ÞÁpŒÂ)‰AG o€P‡ñW BcÞ�bc�ȘÇãt¹ühb�6ÜÔR Y™›L4ý9sê�×jä;�¥�¥¯¦ |å@k(ç˜aYpßI·ûV00VÜn‰¶DÁ“d ôwB™» «Åëv‰aa¨üC Ͳ¯º^öj�Õ�R×Ô,µM #©Öó2<’ Hç¼LéÒ)GºvÌ•NÙðº%mCóŽéOk>ŸUP;ÀÏ ÕˆñzÜn§ß+ñæ�Äã±×‰ø½þ)7Í3‚À‘ÉôAÿgg•á_–ûʈé�•�¸£fÆHŒø¢ÎŒ;\wú²2F’æ‘–T©RÜ¥dGôz@S´Ï{ÕË{›öÈüÕÛå¡eÛE¶îjb?~üPCµî“MÔÈìF0›füÞ¥X®žÜGNS"ãô�®ùY‚A�Ä 9œè>›ªQ@8šÈz"Qa€'žâà²Ûô£ßˆÎdshq*) L¾y!“¥V<ì‘Q3ãHId¾t÷•ª{´�Ú¹kzóWƒ]ÞŒ{ýY§‘Àè�CÅ+‰]¼¢Wž;Ñ×sÉ–Õò·¥ëå¶'ÞÅàm+ˆÙY zdK~†�‚CYÍøèZÔ6ìÓ`± Zâ²{#ž*Dz÷•?_=I._ 8> …chWL“¢¼¶ù†ô&5ºÀ“¯*(z×NÊãtø‚>�–PsË?Ð=¹Å?é†-Hˆ¸sÐ}ùÍKñ¥92VV<¬µ>õðHO¤ÿ•¿tº½·x‚~e¼$•?¨æ¦²�™~ŸG£1ynÑûríí¯÷ZéÖ»«d=ÒMH3zêd¸íìÚ«ïVi £ø‚xD�§dùÜh2bR±cƒŒž|¢<ú½sdH�BAD5�bCÐ&™Åt@A+`h Ni°´Ááðeú]‘¦Æ#ÑY�I7Ì">Ô¶Æãû—áï/#ãŒ82}Æ]ŽThÁo'9=Þ'¼¹9½¢�ÍhâSlSÝl‡ ³ ¸ ‚ù;k妇_“¿¿0Oz÷Œ¶ß)UÍÚjh…Ô4m@™†*%¶ŸµÖ/0ͨqVöΙ>Y¿©íN�,~êû2±7K˜Ø&™Ñ*ÌO�†B‚Ÿ6&H%M<ݾœ ‰T×}�ˆ6]™qêm«µÏ³}k”c¥8f7»4Ç,Cfı2ô ˆH¨üwÿåÏ þ„„�FãQÐУý%(âZŒ ø¼òÁ¾*üÃ§ÐÆ×Ë ¡²§A�5Œ¤ŒÕ;�²Ti†˜W F`\:ıA_)løc­�# ßï’T4)»·ï•sn•‘=¡ 0òБƒf@ž�=á¨S¡P$>š‰8Ñ=�˜×çñÆ#Q‰Å¢·']ÿ¦iÛ  ÇæzÌÀV{�¯—uròÿæÏË›nh¦utt¤Õ½>I´Ÿ~ôÎw £×û;OˆTG¥O¯ Ù߯vÐH~¤»â·-”Ýú+ÇÏŽ &ÛB¡‚@–DPxÎAó½§&"™EAÙ|ßÕRô¡wë1¤€œ´G¨ÁÃäÀ\ðNxšŸ Úòg/EF¡Úúk^¹Øqýk›6&ѱ¹Z½›c“™]ÀXù}ã<Á‚Mþ¬ì ÑÆ–(†T¤ ª‰fãBÖ¥0lsJzä×>ø²Èž&)é™!h§=d‚9Í;@I“˜`ôŒ"³øÌ@�``óšö²`ñFbTŽK0¿éýíòØ?W@�C>-@6óM‹`�¶ùm š¡]&ëݤG/Õ‘Š44ǹÙçF‡Ÿý~- \ì�F6ŽÅý˜ À «ÃÓ¼ð¾óÄãËç󿆛C1Í # ù`˜C‘žü¡J»`u{jÞ*™ÿÏe2hH0?ŠÎ�nÑV ¯ZÁ(g� ²7;k¹‚AbÞMJF4ŽïmìoŒÊ€�½äg÷¾"ìªÔN(;£t¼R˜? #ýn:‚:ç€# 4*rÔ€4 -³'ÒØõføû2³×Eßøí¨c-ÇDØæ�‚tƒùßfe¿ã¬„£ Næ°½W‚ŠÂ’’4¾úÐéÛ¸·Jn¸óé7 Tv7@í»”ÌJ|¦QÝ�»‡[éõ�¡Ö{«·ícq룰˜Â–#FD€ l¿òÜÂ5jPU˜¼X¤5›é)XÆO�¥óæ5ŒfòÆ“7Ò�y½Þ¬d °œâc)_¸Ø¾–÷_ÌËûsÃúÅV<Òõ_ˆÒŠ‘<4ÙÌY´רÐr¢JßБ/úã+Óh}Ö¤¸(P PPbÛO÷â �Qmhð!X7˜ð¹v‚úÈ]|Gv¬UË£ FYÍèLa�Iç¯q („Zð™Ÿ�XS1Èé‰Âüèö¸._ <´fïcÔØ¥·Q9ªwmóÑÛ/¼ï»�¹DšÃ:±{™3]cA5}¶˜g«T/L»{aÎ-›½ãü"©n�AÆÆq1u£o*D6qÕÇøë²Ñ΋ÌÔ1ºI©S6#^Z¸¬0Æ5ñááñzÑþËY¶a&ÈD»–Ãø0�tžD ÿ&Œ°á þh4¢¾ÑNã¸ãñdÂ� —Û??´ð¾ÉÇB¾0°;|¡…÷דó`´ ̧c•ã?ÿ¬ZIz(£”2 ˆÄñýêmû0#¿M2ì)ÕAm¡1µ~* †ÐúœöjMG’3_:½ÛA°tH: 2FãÂZᨵXþñÎ&i€u�J𥯸vêc§Ô|T ˜  j:ÝÎŒÁš´"F L/Ñb°°åÍ»Oü¢…à ›ùMó~=Í > Å,Rꬺ–VI`3>œ‡7¯lwaŽ%dñºé$!½4”²Ò�fú¬Äµ^øÌpþZ£ê»Íx“ž‚‚ „ñ  sÊD2Ïd|B0HŸÐ/Sþþâ6L25`^ ƒþYøXZùZ¹¥9Kˆ&¢]³agÿš+l ªE ‰D2îEç×éÏX~ãž>*ö¢Mxô.G]Ô �ö‹ˆ»}Á—iÒCÁ¸´æÅBƒ°øé;…Z;P.vþ8�[ÛÔ"[±S‚Å90ñ¢å@˜Öv…gÒ+ÚP”0í&¤m£0½}Mß™@�Nî*øª,7 ÚdÄÙ>6eë�Ch(9 ÛNbÒãM€‘àˆ›"b-�5Së D„wÆqˆ;‹Å}�` å .H½xwçJR:µ¬ÐŽÚ¥•)G$—k)¢s¦{Å|Õ øâp(�NäµMòؤBiY~úØ^ÀÃmp°®Yö¬Ù#ÅÙ,Ä1í«G •Æ—‚Rö|>=ÍÏ€nÍÕdÏt±€Ú ±kª21KsÀÅÌ1xeóî*5PÑH\4Üdb`²<ˆrÐ¥ƒ, JëÙ0Ÿh0œðâÍá‡#1nv×p^ðð�»ƒ±4;óþù¯GU¤´T‘‹tžôŒ/7»$ŽF�¯Y/Ô�„·ÅŸL±cÈDV“vìr¢‡Â@ão¥¬4½,’0Ä@ 1[ŸÛ’ȤÆULìÖQbšé$ÊZø1&Ññ¶lÞW‹$ mªè¯áv*›¬VÙˆãYq5*žéèG§´±â)�høy¢M¡¨??orxñïïÆ{Š“gšà(]lL?78m÷±ˆ#\~ÿu`þE‘ÆyR^S<-a:%,l�К¤¡d³!Úžª:ø`’MH"†exX~ÅÎ!ÙÁtÚg`¿�?v*õg˜Ë<)DJH›©ôTdT”d47mÈ[ß¾0€L~.0ï9~Ù´·ZB˜ytÒ¡0á§åIçÓíŽvn�1­Ÿ…goÍ–Ba�Ƽ˜7Rßœðeo Ï¿÷k¦?°ð¨ ÁQÎh±ð‚»ûaåƒÑ†Š6�DÁ-BZu!ÍÒ‰5ZưžÎØñé‰ÂsyÖÞªz¼ø%åHF^ñfäˆÛ1]˜ŒI<Ü,ÑPƒþbá&‰EÀ¥óKZ3± bËC!Á3{Ö–°¨tXÂp'ŒKéxVå˜�„à%¢!‰6×Ãx‘Ny^Y°§Nš0ELì³�_鮼Õn�ÁwF°œ Æ;Tî¬ ‹LÕ’© |FÊ‘Ä4·x³ÑÇÊåDš®Ž¶�~ŽûÑ‘¤éÓUÖS®àþÌ G¤Fttd´4é²Ä|1ó íbG Þœð¡0põ %2޳§š+z`öÍî$]:÷#0åKb#nLIƱš!¦ÄÁüx$$±H ü° ï1ÌÜ¥°"‡\"É` }'*¶pªÖ!lþ¾ú#¾Ûã_0[ÜþLÉÌ+DöI‰ïZ#²9aˆC =*h–iYŒâtƒjøYÿÊ"2_�…ˆ2¨Õ —t•úwqÂNóådu ×&†çÅ2IIeéöH®Ÿ[Tõ;1¨þë}ù9ã"õj±ÁÀEai¬’‘D:É&»`T©¨m‚*­’mûIM}³®¼ÁŠ:É ø%;+C6ì¬ÉÊ–(ˆš A!•´½Ä»Çã5µh-‚Ÿ±±C�(¬µ6É´E,Œ¼ ˆ2.�ˆËOÁÃZA·C/·W°6Š ?·Oïª= ±P£´ÐÁÜ…ÙINQ7¶4CãP˜ÝXgè‘\¬Rö€ca,Zi%@š–—¡  X~ä>ãµqí�ÌšÆýÙY3šÞûߎ)·üÚ ½’ªMªÃzü—+­h¯ šÞSävgluy<ÂOÚ62UM™ÇZ_ŠÊ³ VÊ�³ßÙµzñÃÒn Œ‹ào·^’ÉY—W²;‹?§£xƒY¨•…e˜H ±îQ]‚‰`¤¶ð†`†¸T<“ÆJzg°Ñ„¥µMk²a†,›HØ=$»Ö-CN ©!W×Pé�6+ CKóåÌQ=äÜ ƒeä ] |@ň¹ÀYy[�ê¥EŠ•ÙÎÉŽAœà,Á€À$ý^¯3 ïöU,êã˜17вEbböµmއ�ضócQÇþüÜoBBc¨Ó™êZ$œÌ¯h ÉÅ¿ú«¼5™÷ Y~·ö“ØŽ¢k\�X^š~u©-H•NçËÌ—Ìü" @|™¨¡¤£™ÁÜ Ä*’>‚�Ìœ®�ú™ÞH¨BC[#v|+©Ú,ð¼ïÃåèwÔcFÒ%è;`Ÿ€Â†àËnLSËŽ^*äžÛ¾)ß=ûD´ƒ¦‰cžÊ) Ÿ4x =Í]QÇE;»®-¥*â ÂjV¬*ò„kªï œ|ã¶Ñ Ù‘Ó,�$¥vüÐë�οg„+˜½2†Eõ(7cp,(fÉx�ÿñÿ”þü†ŒÙ_vÔ„”éd8Y@Guh?[^J\Â$Ô$:y* x÷gæI0·P‚†`&Ô6·û!=òjÕ ÖÀÀ£�m™wäkáirgT †ú3_ÍYµ›Šƒ[×HsM…x¼>ø™Z‹$Úä…,4o¹ìU«WÉï¼Vfž>Z0Ñ£PôòÉ=?ƒ QVõ¢d0O fu§‰Á‰ /‰p²¡²Oð̲½¶&fÒÃuGÞÀ®º¤'8ËãGo½9Äõ}f µ†fiNìl«ªóWJ¿ÒÙZ‚-]ùbÅä W;Tx¶�Þú^±½ö CÆÈìôÕìÙ(Õ»“Ú4²:B:@KäŠÛ@sÀ5&5árÓn$ ¦æJP͘þ|'uU&pa,F†‡CÛxƒäº'Þ–óN,•4Ø¢¦E´S)"V^f=Œ¡—æKü1«š›)�Ã�²ÄüÙ™þp"öS|G÷J²@GàŽH´öcIs˼_�óù}gcé6»Î1&eônzÆ.p;„Z`ÖáƒAè0‘™ZLvÊ´3„$ô‘™”Ã/­É<À5`™–†2­¸ i<´Wêl×N›/#W5„CGÆaoÞ‰­\ %¼1}wd¥Lg¯�̇BÀx4+'±–3Îa`K£4×UJ ~ì¢ú3iÚµaŸÑllªBÒ‰H‚rhËÞ¾:ÂÆOËm o*y8½àF¡""öÍò7ñnØYÃýíÐ뿾ÃÂGªŽH¸I“¸:=þÂ@ŽV µ죟a>±Æ?h@x ˜¿rÖÜK.öbcOÓ Uî'èèÏíªã{2;Ò\-‘Ú:qa¯FFÇ>¨íY€ÁáŽnž«Í¬ 1P‚bî‚ãF¯]‡“È”v�pc�Ž´wû�7�aÈм\Pß.ôø]è`Ú·)�ä¦#Žâb†`k ½¡¹ÃJôö‘¿M�Í ‰Í$z*Q€…©9’’î}²%Kܹ¢Y‡z¸h,0V›*Ä5©,ɰÁ.�Iij¾Øf*JOÌ——å‰8R7Â㆕�;³F}T*áñYŽ�ËqB‚Û›"oÞ= åÏÜ |×é}³¬Ëà … Fø1LÚˆaÞOŸ˜//¼¸QŠŠ±„;.݇L“ŽÝûIÚp/ÛoöÞQØ8Æõa\käÀoKFQw dw@-ŒHÝÎ W›|ƒØy‚^0·#î³{±H�àI ûXËTecJ‚C?�øNÛY`À'¦Ñ2°1Rã44–â¨Ú q•j6鬚K\LB)ÉÈ{+VÈŸ~~­\{úîaÔ¦�錘üÒ¹”ÖvF |ò×%`8ËÏàŠx0 cÒ ¢xC¤¶²wö9e‡�õ$­gLºÏ¸¾(ŸÄšžLx}×r²Ä˜|“Úó7yù¤°GnÉÆ=ròOÁ;,'c%Í ‹eЀÑè¼ ö1 ˜£D%aR€ÉêˆÝ=ÝûK÷Áã¤aÜ×e÷ûKd×;¯JÁ€12æ²Û`” Zªv5…$ÔX'-µ•ÒPµ[j÷­“„Ì›•%¾œBñø‚ZÛÑ1P¼T(�³!$‹B“樵Jr%¼]/ 8 àP ñgxVòY!wØ0r°±$‚öŸÌÿþ·/�˦ ‡aŠ €Éa2Íäe„^ôe�2]_飿ÊG¦Ã q샧80òˆù²3³ñðˆq­ÀݘCñÐךO;b-ÿì[2"={nõø|E1önÐw#Y2”Ì_ æO¼âQ),LbYuwé:þbÉÃ�¥€Ô"7M¡¹²­¥&Q>#WÝPåÂ&‡v®—wŸ½S¦}ÿaÉ„€$b¨QÐtL£B„|iŽ4Õ¡—a8´Gj+vHÍÞõ­­X”Å›ÝU¼þl{ |ÀÕÐù!OÕ x¤æúQ4’fe˜ÅPr ñ8L%ã³°«hë6ì3ŒîéÐMþ|Ë4¹dâPñC¨ìI#… éLùL&Ì›!$C\# J“·¹¶ñWGÂç÷¸B-Íë‚'_?Ñ�L¶MòÏÄψeÛ–§HùýÓ½9¹s°ñ‘œtÛ#ÐÌ÷Ȇ}‡dàù�KAA½äô*%S.•@fŽZäTAYÝ¥%˜öР¢W\TÈ ÄuA­×ƒ™ÍõÕÒ¹Ï0´©˜ddb¥Ÿ©¼ÒbgÛÿÍ�1‚~E½¦kª9 �hZê+·Kcå6‰Ã&AçÂ>BOFÚvŒ pìˆR¸Zk-c1/[[ñŽZ �⇠¨„�¢¡AÎ>k \tr©Lr‚tï��MÆêÈa.†eÄSúÙz\†Û‚ ¾Føh;f�Ô¦¢Љ¯~ú˵[¬F¼þŸeù¨iSâ0é¡Í1…ºqâ±—Þ•·¬’ƒP&]*XÕ¢*Û û�BËdp$1¬"<ñ¢B£±C¦–%팑¬ÝÊh„Ûä1´µ‰M jM‡r¼S ¼ú3²E :ž°}‡Ð¾ÎJò™MîLCéÖš†¼9Úp@¨<>¿ìZ].·?$/}˜'¿ÄD¹XÂÎÓc¸~ÁA# fØl! 0P1%Òmq¶b«æI‡·å¥É*¹)§`½"(uVjŽü@g 5'ƒAÛl>îÙ4¤ò¯~峨Áw0o¢/3˜‡IS5àG 6zÈšäÖ»æKïÂfÉ{±fb¨H0Ã. lŠ‹áL¹:Fç*•=xŽÕµXˆEb™g\-é绩 „Ã8æg^XS1¼<ªKj¼“B\Àélý¨%âèCp&‘# NQ¨Ø|0ŽCÅ@V®r;HF‡N’Ósf~' f牓S^4G]Ÿ$ýGN�šµ+ä/ظ‚Mš�Á˜ãÝ4#ÀŒèA‹±,ƒ5[bqÕp-­–Q5–¹³Àx§�‰bbóïÎH4–ôy½½C¿­~sæ´›¯íÖ‚ùG:ÔôÓhÖÕÂ(((�K 6ÿmɈ±[2Í�NÝú�Àa%¬…¿I¯WÀAmj¬Ü+‡önFÇ­ÃÁlÉ.(–\¨zoh“,ªX) al•Ï7hY”ßÔ`ÎÞqö/T¹SíÞ,+·íÀ&RÌ(Â,+ÝòeÄ =¤c·¾âïØU5 ãÒ‘IJlD·Î"°ñWýd²5ÅE~ý^/%oè©’¿v©üäÙÕrÙä¡Ò-£"j*‘‘˜éµ ú y)Ùô•}KG!ß)ö¤ÓšP> YB¡¾LJLjGÂô9ï©ðyçpš�ö Àä) �® 0nóQq#ñÜè±WÔ6Ë]ó¶`($’×o¬Ö¢DÄívkYèÙo€ ý¸;ah©•�óž&LéØo¸ôŸp¡tì1@§pÛÖ"¦7õ€YÕÌ!š ms¸î ìz÷Uùp©Y>÷õ©Ã¥hh6„")•°Ý¿ø�*ü>'�+%cÏæ÷u¥ik 5�6C$,‰L/ü(lÈÊlÅè$·s‰t1Ej–/”åõô˜0ÈìfQ ?>ÁØ<3>Ú°?ä…¾œÂ&½4¦FF* SƒÎmÓžqðÅ*zž`†x“qùùܪª¶QM´O¸¶Âù„ô¶ÍŒµ¯ü¢‡?'³Óáô"òxsØç“yë¶É©ßþ¥ {Št?õj¨[Ìô�bJT+;3‹c\íög˜\¡–#0µÜñ¾¼ÿÆc«o�1—ÿDºôiúFʉ ~„Â; éXó›+w˛޿€Üë)2iP!VîÑ7‚ÊÇÏü9ˆÉ§ÅÊõw-@¼j™zí¯$«+š ) 6Ò $íìl˜7óc–æIï¬��T|°LÞyî^¹æÊóä�ÿüš8Ùo°œ•D16^i Š—�¾,ãë³FÃ[:o“ΆgÓÀ®À=…ÙVÊYí5ôvœúãzx!X¥G¡Ò¥]mEù$£Õ¼Á!8ûÆ‹²cÌzB$)€)Y»�¦^˜p» Ð^?Í²Š¿•³)“‘bœ‹.8¿Îž9Ö½a'[z�$.Ÿ%9°.ö—ÏÔ1;™“怒Äó9žÕóo’!#È¢‡§Ë·Né#�ó3egE�,\±¿eãÎ*阔+§”ÈÛ�O—©“ÆÈ‚G~Œæb7ÕˆT ´ÉÅÔYäªy#€ÐáwÅ8dö�ltÿüÖ= ŠÚP“0.i®@ë++}øc€4d^M±âè9hHy2T3© .šŽáGNÓô¹]BâÀ˜2wn»xÛ®H“­ö‹¤†ó¤.äžÔÉ äÃ^oÖ®÷wTi¾¾Ü"ƒÞLIJS8™.ÍP»4ˆæ"†¾¼â^2ìÌÿ`©åÀVž´f•–wMKŠ&‡ˆèÀm^ü<¤®XþîX~B¾¬ÚQ-ß¹o®œtír¹è9�L>K¦ÌÚ.WþòYywó)íš'÷Í#½‡”Ê/?�~ 'ULŸÆBO nPC àið†�Û ø³ióþQÜg(ú;ä V9qOƒ2 øÛÚ[7,¶ÊN-a˜§Í .^¤£ Å#�ÐNuw­ÙÌ!6(@Jª˜­W –t`5•Ã㬾Ö|MkŒ�jg`‘¦FOy0‰‡²é]Çá-þ¬ÝS£qÜ'kc©±ÔËÄ$ýô©µ¦@ŒCb(-g˜|É/î#ƒÏž)û6/—žhcÑäXip¨]<—7îú@v¬œ'OÜy™”vË“e›Ê×¾÷¸t»ðGrÁÏ£y@‡„Ð!\[vl‘Ó¿÷syá7—È)CºÊýÿ1VÎ¹á ©G³Ó¡ÿX‡™v} smF#?è; œÑZ\�L³°9‘’KÈ|¹��Ër©®ÇbX2‰Å…€ªþ ¥PZ•0PAËÊ}XäÂÍ&`þžCuòáîJÙ^Q#M°¥p¥o·^ÒEŠp�m Ú7!%�NÏ̃Zƒôc³íjÀª´ôý4×>À��"eÌ®&½™™–Å ‚UQ‡®©h„©·XÛE[m¶Þ[¦ª3‰I¢fÈÓŠ$ÕZÜŒTl^¡µmŠÖ%(šö/H…w×®”Çö—ÉhóÖ‡å÷þUF]9Kz÷œ°0m?�Ah+¤_ÿ!’7ó—rþÍ?”÷Ÿ¹^Fõ.�KÏ=Qþ{É[òõ†�ùŠ phr<ðµ�0âh®ê*÷a KÓ0A[�ò‚àÒ5 0†9r1bmÄœ"oõî|ÇÜ¢ï´ÛÌž™¿Jnÿëj‘�‡@ ›…VÊ,ü€{ÿByþ»ÓäìÑýqÊœj¸R X1p³/ òï/LE·¯#h‹¼¦ù¸ ˆ~9R5oþ*íR1\Âaw¯a™É3u¤9!~´ë¬)ZFS^© Ç“âÉ@›Àj]ìø€ÇÀsw�ØBÄ`a’PÙÎH“$k+¤ñ½÷äì±½%/Ó/ïà�jï4œò6HZ0�kœë*`õk�0f䊺—ˆŒ¹JÊWo”L¨ÛS‡öÀ©#Kq°#VøÁ l¬t¤!k+ a–‡eÞ¸èo²øO?�5/?¤Vm‚4s2ÈÓÞ`™wõײ²ä 1`ûÀ"š··ì•^ß~Dn¿w˜Ï>ú<}{Êàñc0t¥V9(ÅXwÁ÷+�â„®i 3Ð�a+Hæi JG7ƱÚäó§9ñÓbÈ,BOÝÞä‘ǵoJ-qà¹}ˆ’鯬NñÔéVƒ›Òƒ°‡ê�Fµ� Z-@+¶°0 íþT±Tùìí×Ãf°ðá{™ I1¡ƒ_ï¢lÔœ1¹y§ŽºHŸ r+Û–¿.þù˜ »Ê¸é·ˆ¯Ë 2dð@YúþR¹ø”áÒ£Í\¤3•ù] aY."N¼ eh¨ªÝ¹Q¶-y^:™(ë–HQß1ÒcèÉ ka¦jº,ñ¦3W‚2í6édš 3jzwË~ù¥g¯Ù)•rãw.–éÓFK�΂Î6žÄeŇÛå‚ÿ3[,ו="½:çÉ#ú`* \q�޾ °™ds¤–b/á„5Ú•Wú„Ëg À\³Ý ƒž"œÑçŠ`;J§šƒ8¨’9¤0SÖmÃT, 8nÈH Ao$IC„qcÈr&¦ßJçÏÊC:‘ ‚¾þª|ÿÒþr΄R©¬ É7~ºX:àH·8&^ªp˜SaÏ,]öÍÑAÓ¡ÊüÂÒ¥fßzL+/“¼ÂîX¶�%»ª°B mjv €æ"y’¨6a5âÍ5 î,‹Nj°Sg�~¦¶`ß„«HC%b$K¿´xL¬¶,€àŸm5ÛüíØôrâ Oƒùe'¬§¬LÎ9iˆéƒ YÄ’nSGÉÚ®E2ôª»%»°¿üä©E2º_7ɽ éH›f €¤r¡TeÖ¬Ö@}Œ³1ü˜ Ëkº¹£þ– x<|=]J ˜d ç:´{¾FŒµÔ)ËôK"ÐñŽÙ�@Ë7>[(jT+>™ÎGÂÖ2‡ëúwVÊ€ŠeLßBVÒ¨õj}&ÌÀq¢È0‘m¶;`ŠÌé¬Í€±@`� æa¨vnA§ã~%$@ѱÝ'n¶¦âüAÚ�k [*+°ž¡ƒÆçÜD ö‹;Ö O-iji ‰ôVI P¼ÐŠì_<»mtÌß ¯Îþ‘œ;yÖ6Ä0Q…UH(—�ñÎe_Cúv•çf]�E2eõÒ�²vÇAt€=*L°¡HëС·Óås¼ì@`/©‰ñi×Ï;µ‹aq ~|âOÛ3ã6 ªŒ.R³<#R5éIFéOÚ;ý`C 7j J·‰Ã‚%0^/=m¬\÷_såÜ;ß�<ü &Ð{nLÓýºw’cÙ‡ˆ£³V Ù�aá詇i˜f` 7Û++eN ¼RYk¦„ýX•¤?–`Û3y¬áì�ä÷–&\ ê¿xä4)ê=Dó@••f.BÁÆ–o�Ø]:fug�SQ(øF˜yæÑ‚µÛ䉧߆Ϲÿç7Ëׯ –(¶ÂÓ±ÚPŸw•@ÝŸ<¬�t4¾aY·u/š9ÂÔ\,¢Zy¡èÜÂîŒ:r O,í­ÏŸpùì&ÀNH #ÁAjí%ÃsXŸÎ³ 0â¥Ðvcá'}˜„|DZ#d¬ñ3A“¥/+Ûb¡ —ó‹¼&’dwÄ‘-åÜq½eÂо"xC’#‡¢CÇ©[¿Œ8çZÙ³þmLîäK×A'I S½ûçÌ“Sï b§dõvÚ.ŠÄƒ¦†Æ(fB\I|ežy稣tòEÒcø$L uÐu�©°¹kÞ³^q?mxo ÂòÈceYnÓ37åd1xÜ]msX~5gÞ‚râ”�rÕYÄ SÇÈ�?æ¦U „™;™ò!XŽé#°Jv¨3 LICƶ‰ÉgŽ=‘OÂG[gio}þ„Kû5€ÁPÁ0ã4ª@’Ý‚Ý %øXYÿÞb´¿{A4«Wmiâ©?BH'f¡­BØÞˆdÇÕ;=…yu)#ýKzÉ ]:˸3&Ë_ž_‚C£ë¥´GG¹óº|ùës/KÐ5�ŽTvÇ.2hÊté3ú4¬×wÊs/½,WœÛ$ãK»aûy³üüoïH¿)S°s`¨aÐ#~Ê^Q­Ø Å$§ «–‹ÛÏ8{ÈI¬jª)‘á}±ˆí7  ‚>Cíó#ooÜ%Ënƒç¹ýª³ÐñKÔN2ã+I4µI O7òê�m´:�¾��ÞŒf3ßN¢¹’xÎ`»ùÚîˆÐmi„�wÚÛ¬B,}þÙÙ°á½öÃ¥è!sŽš5‰è™Â©Ú²¸k´»)( ®Þ¦ô ›qUCàÍ‚Ô[—}µ`–1K‡ž˜|‘äéy›pÌlB¾yêH¹yü^ùï;þ(+7®—šª ©ÁêãõÖȳ?{H.è¸Z~ú�SXIäé`ö*ô|"2‡QªÄÖΗHÙZ‹óœÝdSAƒ ÔŒ„ë«dÏž]rјÊÇ* Јý-Œä#T9âãT<™¿r+FeÄ„“äÄ�½%…v^Õ|›¼­Þâ”ô5öàî‡e�ð”¶*1ðDl.‰³ÅÌ,¬Kísío\ì!rÊ*r6ϼAÎ7@nÊ(•«_’`�aÒ“94ïrÌlb²¤H�›�c½(¼¶pMÌØ™ÏH¨Ùã�kÿœÁ™úí«ä¡Gï€Õ¬ƒ|cR/ùÑŒ 2aàyyÉ3²t5VÿÄR2²—Kn-!§ŽÉÅ32»|»Üýè+2þÂë$€éa]ýø*rÈCk’ÕÄ~GdmÄÝL?—˜öþEÆõ/ÆiãXŠó0la5xB€ 0E;ÏN£¡éFÇ»yÆW4¨ŠÚåÚ/I®ù1NÙA.R)ÞJ¯‚™ýã©rå­[$ÿ½§$Ø¡H²;tAÍAgK…ê”LT0Lo9õ°ßÉC;X³B×G;e ¨ª`ب‘YÝÊøé7Êî¹jý A Öåõ”“÷�f õ¸N�£~Udß¡&yæÕmòˇ^–á§_.N‚àÒ¢gaÄŒð£zç¢.ãÓìQ²}V°û(€]Hq éN(Â1€PmAÕfþ5Œ¥e-?„3Ö½k˜8¸¤+՚楑x±%ÛJE LËM5Õu�òÌrj� š>,<šã(/Ð ˆjt––øcy}šWåS.‡!´YÎÆH3SމùE‡æ‚ñeñUgÈ£Yˆ.écâ> µ,;t¦ÄÚå´è»Q³ ™:ÒÁ<ój^pmu¡œØ`¢Ó¸``ÁÀ“d2†j¿}üø‰ÜüÍSdDI¡bJ˜³s5�˜«Ø^)³þïb´OÍrÒŒaÿ‡ô¦†*©µ–Óðュ¯IvoyK¶­xCvmhÍ»ÍSÉä‹Åßb&À:æ˜>Wš‰6  eŠÃ¿úFT‚d3 ¥ /w»ÄŒ@ ƒ»jfbà;Æ·ïoÝ#²��ÍbÔ«ÐÄUbŒa>SÁ‹½FÎY˜aÅ\õýÔËg €$žr¦|ÎÌÇV“ÚZÈpLîCõ‹«NÁÞÿY2¹Ä½OIßÓ¯Ár/®Ø� ¡lÊdÀ²{üiU…«ùØT´ñ· ¢R£¸}>­Y «ÓrJ†ËY?|Dl^-¿y’ìùvBë^"¥gÌ�žF‰7¯H‡q:ô»?BæWïÝ";V-�Ü¢^2üŒo ·—qDCÄyE;ŽjvoÒ,}úö�¿± sÅP Ï@Ÿ>6{ø=#¶ÿ0™bD€&šågÞðá³:&‚£,ð‘CZ¬f¿Ê!o�LœÚKút逦ƒ} ÆÄ…ÿšž‡“Qa’æù:ðó·Ÿî>[¬ôXèX×v‡ó, 1Pvi!èÇ ®ƒï„2ýåæsdlmD¶¬X$™]JÏ‘§ÀHB"`¦%$î �Ö¢•ù²–‘ù§PIЋaQ »¤µæ ×x°}¼;Æé\¯ƒzæ12¤Wóºq¶€ P˜‡oô7Ddx…Q§îàn¬n”¡§_…miÄe ¶iáÆR5ݸ‚ÕJŰ0V¼ÅÚ±‡\TËø~]¶Æ×4¦Llò褥JMØ8_m¿fÎx¬ýD„ÏÖ�7”|ØVVþöûòÜÜ m¡|ûôa’�¾CK ŸZibœC¿“‡dáTgçÐÍ2!Ÿv5ÍȧĘ…¯Y08Õ®›‘±¢IÑ�6Ž=^'ìØ1éÕ)Wæ^  ¤qý›ÒÒ€NgæXPMeR¦Û_Š�™´u$"ÛR2ºf×zm‡i‰S&!.ÉM£�®%DûíÅ’¯ ,ÙÊèÜ[<è‡`Ÿ†³ÿ †»€ËwÚÑýX ZÔg8T¨ pxªæ6 ]ÌŠaÜ‘–ËȈCÌÊCY>‘%ìÁA’*¦ ïò:iÍÈ)¢ÖÒ¨lß�¦#1•&H`Ý9ö÷¡ß² ßD:óŽÙ’[ØO:�(–ÓGs�%;Žp½YyáÆV`¬oª  pÇ6m|äù3àŽ;ÊH†cO-(UÍ1)h ?»�¤>k±bTÓ‰ŠÀ‚5ºOW¹â’±²u˘Pwj-c¸�R1a¡5Öú(7õ®¯údj‡X\ÓÏ>.ÏPIU ³·ð P(³°®_†Õ¿º qía—a”æfòEZNóê:A0ŒyQ˜‰[Zä-1Žòçt’œN]åÑ÷vâ`K4K–j&êØ<-nž*ÊÍ� G@±Avö«oërNú¨¹¾,+µ…2Ú¢ }†ïÜûŒäâ’ºƒ;å©ëN—B„8ä¶ íDLSòÊ ú„•�f˜�Wo‚ýT÷™€<°´Ìé8ó÷85=µ¥$ÁT[¶ ›0Ä„LÀ�… bht&>ÅF�ÉÔnsfŠŒ›…=S™òA ƒlÆšøˆÊ6»ƒèü°Èµ �zᢒâ¡ïŠt ±‡hŒÍwu|°ÈDüì!+ÃTHp§¶e†ÍG%pjš'•ôí¦uRQcVÀt .™š ¦^|ò@@sÉ«/½%�ýc‰$ËšÎy ΨzaRÇáš²u_µ\ú³‡åµEÁürïO.Ç"–a¥R¡xe€Ò‘Õ‰xA‡<ߥ¸óË¥ípíëLÒ òÉÍ€ñ¶d±|¶ŒiYq±ÐQæ§A=:!V�Êí8¬EM´¬¡Jt‹N6þ ~Ê.¤5…4Ì£/QÔUjùÐÈG™Ù©Ô8EÀÈ£ú+D‹ ÄÌzg¾m™¿•«aº©Q¸ ˆ#˜ÆŠ]º=­+ŒiÛðÁQ€µ‹kaë^5ª„æpâÛš¯ 2˜ËÎÙ´Q}ääSÈâ¥冟ýIvàÈÙo�=QŠ;äjš|ärñêÍòwýU²`ÍÄ>6¹ó–óåºsN„f‚a�xá§0‘‚y‘Zó™%ò�5r3žÐò¶o‡Pû@!2óÄRNq€_ÛyA¢C×Zt¨>ô|‹±OîäSÉâù‹¤ÛÄéâÃ"JZ µÖb›tj  Öš«RÎf†,ôØÿá;R4h¼®Å³Í·„`«2�N*ÁéU)¼ÜFN…ƒ1,‘ÐDŒC¿VGsv;í²å½7$�s�2З`‡’õêÝëeó‚g¥wIO$(Ä×Jë€32@mgM´µsa3� -ðÐõçHé–ÙÒ;ÐGîÿà øÍ–‘'ê'pÞY´Évã×s¹MòØ]WÈåSG`&”uªŸªÎ1S øëçp&ß×Hø´}{\û ªTIçJ%VqO^´0$Ö ÃlÇLŒ(Jvïð8´w«4×�A“.Ä ¥\�ДHÃÎfSu…\3 BËe_Uƒ2™‚M§ x·`‘y8úW`·ú¾Ëdø…Ëà¡%øheLV.YŒ˜L—�_W¹òÒArýyãdD¯"˜¸iÌB>„Eqju&ËÇ•äÖ4ñ¬Óp‹g­q?þ©}`ÙnXïwûk1ž‡Ú­¹k;Ì'ƒ X¼[öA¸vlÒðžð+’šõ 0^¥³sº% qmÂó® Súµo÷¸]Œ‹1v®YA€9´GÍ#�%Þìü•ß ƒ(°Y4‹7íš_:5×äÆŠp™€è8rciL#C¯j'’K i7û h•ûðí×ÀHŽ ¨d”‹+âé;üñGN Ðs�›` Ê–î]²å{Ó¿‰,RRÜ1Kö(’¾Åºí<Ì•?m÷5µ‚Ô ‹ÈUO„Š<’ülq,;Ð�8dš‹g­)>þ©]€ @KfÖ„ÿ~­×œÌuè( ¾ãk¨­„ÁMᕪ‚cÏ1SX ß¹z¼üéñç%cýRé�ƒTÝ› ÀÂ0%†¡° P‡6ŽËÂöbZwßê…2à´«$«Cgíå“u¶3„¶°1H »‚�M£Ù =Ÿ�6¶—&†fBî±//BcR” ªó͘s¦¹™*$š.Œ|9bà^Dº0Ž´Ç÷¬Ñ¥1WF¦@qF‘#�zh�»ÿú–ä£ÙؾM~Ówäü‰C°v 6€�ï'b©=fÁ|ÅÇ‚dnô3¥ÐPÅ)•túqHActM'|¾ÞœáÄNÄg;äØNW^nÅu,ÔÞ&ê…PG”ÌŸâƒgu pRêGÁgž‰Þ²t“æ5Ïêz7zÔ$"Ï?Ã@D1R U}%¶�­|ñ>é4p,ÖâMjMƒèÌž¹†á˜>—ríX=ßb>b0®æi¥Á»=Â0ùš¾!éê!Öh0ÁÔ3¦5?ö}ôYñ‘.Ìvwõ1)|Ôz„€†~äºüÍ÷¤Î%è‰Í,'•ö„Ñ +˜¡ê¹¼>„Å«lí¡i ¸é3Czš'몎CÊX¹´³ȸík™±;{âN‚§‡i /†&†HÄ›mß�îEòÈ/ΖÍqiyçÙ¿ij6k2JÈLw¢æ“A }â†�}WÈŠ¿?€U9¥(#�oåBCÞL±Þ-Æ�ôl6bcI 6íÚÏšMAã0ÎØXóYc™ÞÀ°ë/ñ6>|°s$�1!„Å&_¯ˆ®L0€Ò�Bi•Næk*s¦<¼ùS‡É�›¯�ÿó›g¤oè~i©:_:õ£C:ÝÒ�ØÅWól]øœdwé©ãïÊmïÈú…Y2hÚeº•ܘtíº‚Ä†cJxZk*¶áð(³A”„åF#“€‰˜B᣹‰8Ì£š¶8G~ÃFC|0ž \¡¥á–šïS{á¹µÞçHÀE �IG‘qB+/\¿Sž|s�Ì^¹ ‡Lã å]3°Ô?S6V6bM@/MCþj2ûôSH¤:k{¯·hk²a$l ÷8#áðfßÔ ]𞼷ǵ['뛿-å翆qØu)ìKaÕ8lÊ`0W)&‚pª.�8놥úáEt–næíǘ¿HÁêW$Ðs,Vìö@íòè uvÊ^ìø¡ëP2DJ§^†Crdùß�=ï¿_§”Z=s2�ÃAÛi]e›‹yžM`ÏBr–l?ŽyÝ»q9v&¯Ó#è;÷ ›þ8�Ú¥y—x[hê(eš¶€pSƒlÅîã=ë^Ç™=!XY˜ª–@ó&Äê�VAžBS@AqÃöäürÍOVÔ p E&>=WS³[*vc²®S‰ü|ö|¹ûš3¤svP¨*ݛĴ0ÑÚ®ïôkuLâ ½��•XäjyY©ßæ»Ì­±?ù©Ý  ,Õ¤žÆ×¡wŠEFå‘ti©edbÇ.¹åØ“åܼ~ßž6JÆCúï|z‘Ì]¼W:îZ.»ßyÅŽª=ýâ¡“¥¨ß()G²{ô•îC§AÃì“õ¯=![W¾..¹ “óà $V@o£tTâ�v*�¤kõoQÉÄÓl"àüDr޾†úgüÃ[µ&ݸ8´ä�ü~_)vÜ�ͪ‡ÓÒJI ïA u|×zA»8:Dè=äc=\E-jBÕ&Éšô=mfÀðM`Z|ØpÉ=º DI�1$A˜Œ�lzû%Ù¹ìrðÃw¥3=K&FA¦áé Pí<—(§ó ²ã�¹òþ%ФeͤQ©ÿ©WÈÆ7Ÿ‚Ö"½FNlÊ8 Ïr .SßqS!P§m6£.>«ŸÞ‘í×L“QøPD ¬‚7žw’Tj‘?<ñ¼ôÂȇ&!­Õ§¨«•MhvØíCwÖ/r60F¡³™Írª^©ô®a©¸+Ãï‰Ö„㉠+~*qÐTyXŽ%;l7òå Õ¾dèþHCSâÞ*ðÈQÙ0Ó0?€%\ïnÝ'ßúÑ Ò»sLòOž)Y8�+ Fêl–’šDZ–XµJ?™`O£kŠÞ{ïl*Xki; ù¶sß2è”Kää«À13'aqg½Üò.xbç‡sÄOá™{�a“%æåµ/?ûÄJ…£ë ÙùB^ì$|cZÅšÁÈ8ž1ùÃK«¤K¸‚8»Ø ažŽ}�|y¨zý�²kÅëR—Y(Ïþö™:¨'ö'¶]èaO3%ó#þÔÁ,‹S-£ M¡T4ü½FVž˜ðö_Ó §ýI°Ò¨¼\µ@ñI7ÔÝ~õ×:á¼Ú± À$—2Ç"ñe]¡‰³ Ö®sîzQ�ní}ú ºdœmºa¾•»òÃW ¬…G˜2w‹ŒÁpzstaÞM:H‚Ölúû±z· × íôíY¹‚ãÔçhLR5mÀÃ#¸÷�ý†½.�}«ÊqÎ0—쀃¬9ºÐUGdò#~øStðB8,à /�»?�·ÖÖà+2QɺåùeeÖ}K¥s±_ŠGÍ�uÆòŒ×�]k—ÈÏ¿}šüçY'b¸Ê!(Êa ­Ä€Á”×–¶;bp”•%­ïÕìo.ÿý¯ý¹Y?‹à›AÀ6\ET Æsn–oÝ/?ùõëÒ�gõ�8SWÙ&Ppvò3]:¢¯� %5ÍjõÙ.š …ya�!ÙTP”R‰è…?jm–xBއãj¢Í þhÀxþLÌïs‰5;–XKˆ¿<ŽöÄé7�ù«`4zKÖ/x\ÍÆS¯ý�21&”!èÈqË9G§üçýzúh´G©tè;Võ9‰�£‹p$~^�°DÀ‡£‰ltݰnRE@_K°¼SßÛ†4 Ã5îÆçÔcÕuÿ¥GÄ£öƒ‡Ýö+0\ŽXÀÈ™*ÖÁMïÿ<2`è7|�@/쎡*Ò¦…öí&Ìlýöy|!L*$oôÍ:–ÿÈP …ÖŠk^x”’¯z�pàÃg«‚“”J Må$ß5€ñ5 B€0jÊñú°3¯†È–�0:U¢¶öw�tìUªÓÓ´ësÙ:µî‚ÑC3N QK>EÃC¤9›gòLNOsÃȈsÿSW9qÖ‘3ƒ{UÍ`âw—–W‘‡QìÛˆã`0L=¡0ŠþÄ÷4öZSô‘e×òH‡H±ç¿)8ùû÷h£ ,äûö¹tH¥Ì·ëbá…¿½»,_cqTC­yؽj«<÷××dȘI’ß{˜eHAó�;‹gwÍ›^•p¸p|ŒcÉÒ!$•a¹•Ü"a100mĄ̃æù¥±A§}«è/›–½ k^üƒ~µ¤ÛЯ¡Íï¢mz¾;ppëri>°Oòz’!˜~ö3L-F;­ŒbÁ!=‡MÔÉ mjt*—{`» È²\dœ–�ØBk ?ÑT±U*+vÉ�×\$…X(j¦S,Ø%4R�ôÊvÛwÄ‚±mf4Š t$>—~»ÀÄnXRÆ�RTaƒÇ�íkñ)¸ùzãeá,/Öû•^|©t„šgë�P€­ÎÊŸ›EðÕ"ȃe3°ò6˶LÿÀîË0%g6y˜DãÆ% æœûâh8·´À@Dûˆ‚ײ2.‘·_Ì;ÊóÃ`®©,0å¦y¶AÎ uä×Ï-šµe€8tpÑ÷:8&Ÿæóùº¢ö%–mÚëzíå7eè¸S1£×Ûš˜ÑVϘ‰YvJ:ÊKBÑæñͰOÃìWÜÓq5ã𡵶¨7|è”_æ ™ØtinæWIŠ î£/Tà çÖ¾ùb' ½²tJ÷5óh“±v>ÓïˆÀgÞ˜9ž+E´ûü¬]Å‹eÝÊ·äüóN—Ñ}»ér/=t±Û(8“Ö‚¥´€Õ ÇóPõïño_©µ_ŽÀèCüþÕYÜøWïÃ{‡ÄjSÐmÆÜ�#ºœªŸZwÌYd6•äö¯Ó·T›­jÙ°Í¢›fHÉ'ÍØ.’ÀJRøiÆPjXÚMlMŠ bÀôæ»åÇ›Âg:ÀQÓ.F \ËÏ#_²±¾Ÿ»™Gœõ]Lí”eOÞ!;W-Dû�ùôÒs„ke‹@”ˆ³úáAžôã�ÃL~ææÐî�Ò°b6|:Ém3ÆK:Ǻ¨ÎŠÇØ,™¦T¢ØP=ØÒ&]æøÖ“aUý:ñ‚ÄŸÓ Ú G8å–Å8£ývl”p>ô—Åñ’Òá8Nµ—¶™$¼ˆ ,Î+‘ðjìà`‰gPý�Q-Òh8^Ô¥a™WfHDb<�«d�ÍŸižlF[=ÛïN½°Ûø’2ì(ê*Ìûƒl\üF M*À¶iÚâ¿�« �Š¥âI|a‹P¤1- [Â!45 Ë•�»ä‘_^$#K0?�±¿)3q4¸ý#¶|Òk*÷eg8áæŸ¦Ü²TG^hvÓ™·#2}Rž³y™]e׬?¿¼È̼tÅ{ƒzŒ?/Ú¡g©K'C”ˆ¡Æ‹á·2ÉÐË]áÛq ·l)`’t:}f<}ÐTÙE€%@oõJû¥k,?޲qf€Û›£Õ6i¬Ú+±}ëÄÀ÷°÷�GÏÑ6ÄÛfÏ0ö7h•ŒanààÆw¥nÑŸäœ x×�¾)ß9s4:¤à ?$ÞCßo¢#êÏ zC5µ/'ßø}àêt�:"ƒO›¬>òxÔ4€ µ¬¬L±ÿÕCuWõîž½=¯Ç ®– …´ˆœ¦˜)q+GSk€Æï¦&3”OJ+�ÅDšPßxa’¶>ªU˜@= ³‰�Øpìøœ—ÏêÔ]"8‚>³C±lÁÞ¿¥O–ÉÞ…OJõV¬/À¼1 “¹*HWÁÜ˃§i9ä'gª·­‘}°#¼ûÜoð%ô–Ã}Gn:o<º˜Þr5‚H——¨¨cqGÌ�¥‘ú†õ�?Ü0]ÃfͲ"½ÛÑé¶Á�,›<Ù]VþFsaFîÔ`Fö”4-9Õ–[GõÅYL3VZ(sÈ£�ŸòZôZk«UëYã³1Æá¥ë#Zµc‘ØænRÑ)Ðû¢·¢dŠŸ½ÿX'PäÅáÒWÊ#/­‘òyoà« ß"ÈG˜Âüs3¥íOo‹ñ¤�úÙðB�´ƒ¿åaÇkŠt$´ÍØËàÛs�¸/!;ÿx�ßÚ¸[^[ޥ܋6c3ˆÙÑšªÍSN‰\sî@9\?WÚCrqœ<�ª5j—FÃY#¼ZlGöz<îH$Š…Â#²O¿e#gúFÍœyÄÖ¾6˜ý�G¢ò…¹‘×> !˜Ã#£ó3³Þrãûf*\ˆœ9ôQF(xRÎtX+µóû¯ÕÁêÅ;þ´–[qHNõ%Pº4¡Í«}mlû…�aÌ—ß1Øøê£²K»6?w�ô�ÕŽMX#™*œ ¸»ªNö`7ÐAœ8ÂÆs³Ò­ GÐæHç¼,lîÀ¾´÷q-íºa¶âÀò"Oƒ7Qóñ¥m|i4– 5ŒË8ý¶•Ÿ÷óðvù>é~Ô›€¶‘ù×>¼ÂóÈÌQïM¼é/'å:e©Çëõ`.?†Bc ™<2D A>²†ÞüÓ8 Åϼ¤åĶhž¡gÀšJ…0&ÂÐy‡3¯æÙº*O@³­îÄT2…ƒý½¦l9oß'¢uÅ©`‚ JfÈ ¨ˆC=D¡¦–âAŒO5ô>Ÿé8efʃÈô#X'­ïÁi¡‘d44ñX0Ÿ¹õN �¶u`¾ Á’û®Zž‡ÇÄ"‘F¬«çÀÚ¨4Öºt-0R‚(QLÝ /§G•Vˆ›v ™«)d†_ˆaGÃ�qlFi-gšÈ@³k'ÃéXÛSèÐað�Þ>×»0#�Ü£Ïa\ŸÎãw�¹©ƒ[·¹ÿ�L�`S,¡/ã¬&ÊBŠo¶£À�¨³fÑH¤Îj•1í–÷¾èšoçÿ… 3¢ŒD;ö꯿µ&Y]7, ïÆ¶j® BÈò£VéÌ é‚Øtk5¾€d*( C$‹~6y•gôÆÏ+Š.…É|®qø‚�Í Í—C3+€@uˆ³–$“IÌPâÀ~ûžºZïvh 6æ`´�Ç|�#ûŠýõe¼áæ¦mÍ-µC|§ÿðƒcÅ|"pL€­D'†}‚WþtÝöŽûvе4-Á N[p ŠaDÍÎ#‚<ʼØ4SÖ>bîv$MÃdFý꫆ñÉiK|…É âÍ£)&á3® ˜ÏE#\ïgcfbšt”×àI¡`m6ÚÝJ¡þ˜)~ÄSß¹€«zs‚Þp]ýüÚú†t8ýö=Ç’ù,ÏÚ`mû“˺/›‚-2ròŒÛŸþ6X|?ÆmÞXÒxH’ÊÐLŸÒÏ6=Óm©F2Œ°H®q #MZB#ù TµùL?8»96Ì;S2n#é„eæXäImdaf ¦�æ2¾:ÜT°Òáxg€2Þà„·vóz\>Ø ªkï Lºá‡…3«£¾˜Þ>áœ;fÀμ¼lJ|úts|Éœ_|ãzGcý ̉6c¹¶ö À.Ô ’¬• vZÞU¬P›æépŸqÈm8†kŸÀŠÈ´ûYƒè�Ÿjzà…¤ª¬­—’nÙzÔ ;�L̬הB4™i¨‰‡+ÄÒ ÖWô%ÑœÄ|YA޳¯o©­;Ïæú¿eNšÓ5Þ1¼s`ÙæÎ¥A#å 6øë¯®œŒ†ú&š_ÇÚ<&_ˆ׋‘ÖJh‹Ÿ|µI¬mWk[kQYo`#™¨©5 Ù`xÅ£ÏíXÚ‚C®qãHËîÝÒ§€bôj`"±‰cÁ'w-ÐÚX/ì°¦˜‚Ö&XCq€µÓ‹oÕD_Š5Ô÷ɘúƒ¹ž�qŽ2šÑ�¹;¦MÀGKçH•—Iœý‚ÿ{ÏìGØÝöä·’nïo0��‡6ÔÎ\hÊ-…Z“Hk›¯ZsÓÉtCre�2Ű�ñÙØñµù°˜FO…‡‹y2„‘¤ú—zéÞß ÆÞ =ÍK«³¦>d²�k| 2„¯¨ùöº"ÜåÏÉôDê¥Zšo L¹ñiÆ>Öí½Áð£×/E´E�ý˜��‚fá¯w}ó‰ÌCu½£ uá˜F¥€²ñeGѨ󶉉ï6ìIº+³ŒP˜X†¹d޽˜^ô5QùD†³s¼ÅœÜ /¦ŒÙc31˜�ðqAB2œ�ÄOï|6cÇTÒ—À7=SŽHMõƒ-kJÈ|à *ÿ·÷6bmï_¢hEƒó|£6xöO3kñxÝù·Ìþ#¬hw:ýþó½^Ÿ3†�= ¡Œa´ b³ng˜hhØ ãÙ†1Ö|‚tÔÁp7õf@‘—ÅÎ_:~¨‰­R V.RµØ­avS£`T#¨�ñNtiðÕê|¨oø»+Þ|»ê�u³ nà˜1ã 1ë*R‡yùJ€�3µˆê˜2«ÜõBÙΦ\pÎÍOŒŽyC·bñÝEžŒ,�hSË ze`"Œlê|pg­´!âÜäkªÖ~Á[×áÃ_;‰&•¹2 –¬‡p8]W4ˆdÁ´ë¬7B�øáš@HÛÃ=ìÖI4·àCRõ/:ãÉ»¸i“pÈxÌ�°IûÊ0Ÿx}¥€�@¤ªŽªnp¼Tö­÷ð>ýœ›î�'fâÌâËÜìÜŹ2\EÏ™It-5Œo6“Äk˜�L¢ÖpúS�ÓZG!H j2Ϩڳ› ûH1>Åœ, `*a·=‰´X÷”òøü~,p¹°Kª!RWÿœ#~Ð?í‡:[´C»ªªõ©Q30¼›ydk÷‰öåZ©õEåð9árÈX5°ÀÁá#A]}õÝY‡:~=åö\îMÁÞ<ì»BMF¯M­a+ x fiw\Õ6=•w lu:löî5Uî•yÞ(ž†<ùƒóR®³Å0Ê ‡A` :ïèFqô loc¹ÛÓXô97ëÌŸà ` †ºs÷0Në`ºcí¾rà_  CF’År™ä´ŒHÏ Þ3ÓoúKq4ZsZÒí: ‹÷Æã4°Î˜gÀ,óÂz<ìïgE‡]�SGÑÞ¡i&¨!ÒŽšƒ±°E'R]AypLÜÕô¢ ™òἚ„1£i5G±•ŽDâUL¼ä›v 7«ãj]™¼'§ý¹{;�£ygAÿÍí宂ҪÔÜ6µëÚkË‚‡r{ �¥elÜ僭[¡æ{Àð’Ë\–­ó V]×Bƒÿò^› ¨ZüvÎ{RV-x^–üåg© ¥=[B¡ÈLmAë´*‰¾tÆßsL¹Õt�žg)ˤINÁJ]« û·¡é¿¡´ÒÖÖ "‹¤¼ìZѦßôh¾×ë*n–dפÃÝMy‘Ëéʉ¦RùJàsƒXÀîãÛÀÉ0¾Õt»jZš-y¨êƒFÏ8c[¿¢ 8̯’«rZs¦À€éåd:kû—cÄi‹Ï‘>ÿ[ ÀG �rLŸ>×ÉþýËËx�…v(?íß8vçÌå¶E‹$ áûR,wGˆþ'&û_$SF4øe³Ä±¾t®£jCûNÏV(å³pÚJ*ù@ÓIÀBWªM‡ácò:îuœÇ)pœÇ)pœÇ)pœÇ)pœÇ)pœÇ)pœÇ)pœÇ)pœ_e ü?¡€bKÖc<›IEND®B`‚