├── VERSION ├── MAINTAINERS.md ├── .gitignore ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── golangci-lint.yml │ └── container_description.yml ├── CODE_OF_CONDUCT.md ├── SECURITY.md ├── NOTICE ├── fixtures ├── json │ ├── tasks.json │ ├── zones.json │ └── server.json └── xml │ ├── status.xml │ ├── zones.xml │ └── server.xml ├── Dockerfile ├── .yamllint ├── .golangci.yml ├── .promu.yml ├── Makefile ├── .circleci └── config.yml ├── go.mod ├── CHANGELOG.md ├── README.md ├── bind ├── bind.go ├── xml │ └── xml.go └── json │ └── json.go ├── bind_exporter_test.go ├── go.sum ├── Makefile.common ├── LICENSE └── bind_exporter.go /VERSION: -------------------------------------------------------------------------------- 1 | 0.8.0 2 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | * Ben Kochie @SuperQ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | bind_exporter 3 | *.tar.gz 4 | *-stamp 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Prometheus Community Code of Conduct 2 | 3 | Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a security issue 2 | 3 | The Prometheus security policy, including how to report vulnerabilities, can be 4 | found here: 5 | 6 | 7 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Prometheus BIND exporter 2 | Copyright 2015-present DigitalOcean, LLC 3 | 4 | This product includes software developed at 5 | DigitalOcean, LLC. (https://www.digitalocean.com/). 6 | 7 | This product includes software developed at 8 | SoundCloud Ltd. (https://soundcloud.com/). 9 | 10 | -------------------------------------------------------------------------------- /fixtures/json/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "json-stats-version":"1.7", 3 | "boot-time":"2021-07-15T05:11:08.926Z", 4 | "config-time":"2021-07-15T05:11:08.972Z", 5 | "current-time":"2023-04-08T17:09:34.885Z", 6 | "version":"9.18.12-1-Debian", 7 | "taskmgr":{ 8 | "tasks-running": 8, 9 | "worker-threads": 16 10 | } 11 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ARCH="amd64" 2 | ARG OS="linux" 3 | FROM quay.io/prometheus/busybox-${OS}-${ARCH}:glibc 4 | LABEL maintainer="The Prometheus Authors " 5 | 6 | ARG ARCH="amd64" 7 | ARG OS="linux" 8 | COPY .build/${OS}-${ARCH}/bind_exporter /bin/bind_exporter 9 | 10 | EXPOSE 9119 11 | USER nobody 12 | ENTRYPOINT [ "/bin/bind_exporter" ] 13 | -------------------------------------------------------------------------------- /fixtures/xml/status.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 2021-07-15T05:11:08.926Z 6 | 2021-07-15T05:11:08.972Z 7 | 2021-07-15T10:25:39.396Z 8 | 9.11.31 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /fixtures/xml/zones.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | builtin 9 | 123 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /fixtures/json/zones.json: -------------------------------------------------------------------------------- 1 | { 2 | "json-stats-version":"1.7", 3 | "boot-time":"2021-07-15T05:11:08.926Z", 4 | "config-time":"2021-07-15T05:11:08.972Z", 5 | "current-time":"2023-04-08T17:09:34.885Z", 6 | "version":"9.18.12-1-Debian", 7 | "views":{ 8 | "_default":{ 9 | "zones":[ 10 | { 11 | "name":"TEST_ZONE", 12 | "class":"IN", 13 | "serial":123 14 | } 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | ignore: | 4 | **/node_modules 5 | 6 | rules: 7 | braces: 8 | max-spaces-inside: 1 9 | level: error 10 | brackets: 11 | max-spaces-inside: 1 12 | level: error 13 | commas: disable 14 | comments: disable 15 | comments-indentation: disable 16 | document-start: disable 17 | indentation: 18 | spaces: consistent 19 | indent-sequences: consistent 20 | key-duplicates: 21 | ignore: | 22 | config/testdata/section_key_dup.bad.yml 23 | line-length: disable 24 | truthy: 25 | check-keys: false 26 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | - misspell 5 | - revive 6 | - sloglint 7 | settings: 8 | errcheck: 9 | exclude-functions: 10 | - (net/http.ResponseWriter).Write 11 | revive: 12 | rules: 13 | - name: unused-parameter 14 | severity: warning 15 | disabled: true 16 | exclusions: 17 | generated: lax 18 | presets: 19 | - comments 20 | - common-false-positives 21 | - legacy 22 | - std-error-handling 23 | rules: 24 | - linters: 25 | - errcheck 26 | path: _test.go 27 | formatters: 28 | exclusions: 29 | generated: lax 30 | -------------------------------------------------------------------------------- /.promu.yml: -------------------------------------------------------------------------------- 1 | go: 2 | # This must match .circle/config.yml. 3 | version: 1.25 4 | repository: 5 | path: github.com/prometheus-community/bind_exporter 6 | build: 7 | binaries: 8 | - name: bind_exporter 9 | ldflags: | 10 | -X github.com/prometheus/common/version.Version={{.Version}} 11 | -X github.com/prometheus/common/version.Revision={{.Revision}} 12 | -X github.com/prometheus/common/version.Branch={{.Branch}} 13 | -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} 14 | -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} 15 | tarball: 16 | files: 17 | - LICENSE 18 | - NOTICE 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Host operating system** 11 | Output of `uname -a` 12 | 13 | **BIND version** 14 | Output of `named -v` 15 | 16 | **bind_exporter version** 17 | Output of `bind_exporter --version` 18 | 19 | **bind_exporter command-line flags** 20 | Please list all command-line flags used. 21 | 22 | **bind_exporter log output** 23 | Please include relevant log output inside a text block, e.g. 24 | ``` 25 | paste log output here 26 | ``` 27 | 28 | **What did you expect to see?** 29 | 30 | **What did you see instead?** 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | # Needs to be defined before including Makefile.common to auto-generate targets 15 | DOCKER_ARCHS ?= amd64 armv7 arm64 16 | DOCKER_REPO ?= prometheuscommunity 17 | 18 | include Makefile.common 19 | 20 | STATICCHECK_IGNORE = 21 | 22 | DOCKER_IMAGE_NAME ?= bind-exporter 23 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2.1 3 | orbs: 4 | prometheus: prometheus/prometheus@0.17.1 5 | executors: 6 | # Whenever the Go version is updated here, .promu.yml should 7 | # also be updated. 8 | golang: 9 | docker: 10 | - image: cimg/go:1.25 11 | jobs: 12 | test: 13 | executor: golang 14 | steps: 15 | - prometheus/setup_environment 16 | - run: make 17 | - prometheus/store_artifact: 18 | file: bind_exporter 19 | workflows: 20 | version: 2 21 | bind_exporter: 22 | jobs: 23 | - test: 24 | filters: 25 | tags: 26 | only: /.*/ 27 | - prometheus/build: 28 | name: build 29 | filters: 30 | tags: 31 | only: /.*/ 32 | - prometheus/publish_master: 33 | context: org-context 34 | docker_hub_organization: prometheuscommunity 35 | quay_io_organization: prometheuscommunity 36 | requires: 37 | - test 38 | - build 39 | filters: 40 | branches: 41 | only: master 42 | - prometheus/publish_release: 43 | context: org-context 44 | docker_hub_organization: prometheuscommunity 45 | quay_io_organization: prometheuscommunity 46 | requires: 47 | - test 48 | - build 49 | filters: 50 | tags: 51 | only: /^v.*/ 52 | branches: 53 | ignore: /.*/ 54 | -------------------------------------------------------------------------------- /fixtures/json/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "json-stats-version":"1.7", 3 | "boot-time":"2021-07-15T05:11:08.926Z", 4 | "config-time":"2021-07-15T05:11:08.972Z", 5 | "current-time":"2023-04-08T17:09:34.885Z", 6 | "version":"9.18.12-1-Debian", 7 | "opcodes":{ 8 | "QUERY":37634 9 | }, 10 | "rcodes":{ 11 | "NOERROR":989812, 12 | "NXDOMAIN":33958 13 | }, 14 | "qtypes":{ 15 | "A":128417 16 | }, 17 | "nsstats":{ 18 | "XfrRej":3, 19 | "QrySuccess":29313, 20 | "QryDuplicate":216, 21 | "QryDropped":237, 22 | "QryRecursion":60946, 23 | "QryFailure":2950, 24 | "RecursClients":76 25 | }, 26 | "zonestats":{ 27 | "XfrSuccess":25, 28 | "XfrFail":1 29 | }, 30 | "views":{ 31 | "_default":{ 32 | "resolver":{ 33 | "stats":{ 34 | "NXDOMAIN":16707, 35 | "SERVFAIL":7596, 36 | "FORMERR":42906, 37 | "OtherError":20660, 38 | "Lame":9108, 39 | "QryRTT10":38334, 40 | "QryRTT100":74788, 41 | "QryRTT500":69536, 42 | "QryRTT800":4717, 43 | "QryRTT1600":1034, 44 | "QryRTT1600+":39346, 45 | "REFUSED":5798 46 | }, 47 | "qtypes":{ 48 | "CNAME":28 49 | }, 50 | "cache":{ 51 | "A":34324 52 | } 53 | } 54 | }, 55 | "_bind":{ 56 | "resolver":{ 57 | "stats":{ 58 | "NXDOMAIN":0, 59 | "SERVFAIL":0, 60 | "FORMERR":0, 61 | "OtherError":0, 62 | "REFUSED":17 63 | } 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/prometheus-community/bind_exporter 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/alecthomas/kingpin/v2 v2.4.0 7 | github.com/prometheus/client_golang v1.23.2 8 | github.com/prometheus/common v0.67.4 9 | github.com/prometheus/exporter-toolkit v0.15.0 10 | ) 11 | 12 | require ( 13 | github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect 14 | github.com/beorn7/perks v1.0.1 // indirect 15 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 16 | github.com/coreos/go-systemd/v22 v22.6.0 // indirect 17 | github.com/golang-jwt/jwt/v5 v5.3.0 // indirect 18 | github.com/google/uuid v1.6.0 // indirect 19 | github.com/jpillora/backoff v1.0.0 // indirect 20 | github.com/klauspost/compress v1.18.1 // indirect 21 | github.com/mdlayher/socket v0.5.1 // indirect 22 | github.com/mdlayher/vsock v1.2.1 // indirect 23 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 24 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect 25 | github.com/prometheus/client_model v0.6.2 // indirect 26 | github.com/prometheus/procfs v0.19.1 // indirect 27 | github.com/xhit/go-str2duration/v2 v2.1.0 // indirect 28 | go.yaml.in/yaml/v2 v2.4.3 // indirect 29 | golang.org/x/crypto v0.43.0 // indirect 30 | golang.org/x/net v0.46.0 // indirect 31 | golang.org/x/oauth2 v0.32.0 // indirect 32 | golang.org/x/sync v0.17.0 // indirect 33 | golang.org/x/sys v0.37.0 // indirect 34 | golang.org/x/text v0.30.0 // indirect 35 | golang.org/x/time v0.14.0 // indirect 36 | google.golang.org/protobuf v1.36.10 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This action is synced from https://github.com/prometheus/prometheus 3 | name: golangci-lint 4 | on: 5 | push: 6 | paths: 7 | - "go.sum" 8 | - "go.mod" 9 | - "**.go" 10 | - "scripts/errcheck_excludes.txt" 11 | - ".github/workflows/golangci-lint.yml" 12 | - ".golangci.yml" 13 | pull_request: 14 | 15 | permissions: # added using https://github.com/step-security/secure-repo 16 | contents: read 17 | 18 | jobs: 19 | golangci: 20 | permissions: 21 | contents: read # for actions/checkout to fetch code 22 | pull-requests: read # for golangci/golangci-lint-action to fetch pull requests 23 | name: lint 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 28 | with: 29 | persist-credentials: false 30 | - name: Install Go 31 | uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 32 | with: 33 | go-version: 1.25.x 34 | - name: Install snmp_exporter/generator dependencies 35 | run: sudo apt-get update && sudo apt-get -y install libsnmp-dev 36 | if: github.repository == 'prometheus/snmp_exporter' 37 | - name: Get golangci-lint version 38 | id: golangci-lint-version 39 | run: echo "version=$(make print-golangci-lint-version)" >> $GITHUB_OUTPUT 40 | - name: Lint 41 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 42 | with: 43 | args: --verbose 44 | version: ${{ steps.golangci-lint-version.outputs.version }} 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.8.0 / 2024-11-06 2 | 3 | * [CHANGE] Drop XML statistics v2 support #171 4 | * [CHANGE] Deprecate collection of task stats by default #200 5 | * [CHANGE] Update logging library #212 6 | * [ENHANCEMENT] Add metric `rpz_rewrites` #208 7 | * [BUGFIX] Make log level configurable via command-line flag #182 8 | 9 | ## 0.7.0 / 2023-08-11 10 | 11 | * [FEATURE] Implement JSON stats v1 parsing #169 12 | 13 | ## 0.6.1 / 2023-03-22 14 | 15 | * [BUGFIX] Fix unmarshall error for negative values #166 16 | 17 | ## 0.6.0 / 2022-11-09 18 | 19 | * [FEATURE] Add REFUSED label for metric bind_resolver_response_errors_total #125 20 | * [ENHANCEMENT] Decode resp.Body directly, without ioutil.ReadAll #84 21 | * [ENHANCEMENT] Update exporter-toolkit to support new listen options #151 22 | 23 | ## 0.5.0 / 2021-11-23 24 | 25 | * [FEATURE] Add support for RCODE metrics. #113 26 | * [BUGFIX] handle non integer values for zone serial. #97 27 | 28 | ## 0.4.0 / 2021-01-14 29 | 30 | * [CHANGE] Replace legacy common/log with promlog #85 31 | * [FEATURE] Add current recursive clients metric #74 32 | * [FEATURE] Add zone serial numbers as metrics #91 33 | * [FEATURE] Add TLS and basic authentication #94 34 | * [BUGFIX] Use uint64 for counters in v3 xml #70 35 | * [BUGFIX] Fix Gauge type for large gauges #90 36 | 37 | ## 0.3.0 / 2020-01-08 38 | 39 | * [FEATURE] Support zone stats, enable some initial zone transfer metrics #49 40 | * [ENHANCEMENT] Better flag defaults #50 41 | * [BUGFIX] Fix parsing on 32bit systems. #58 42 | 43 | ## 0.2.0 / 2017-08-28 44 | 45 | * [CHANGE] Rename label in `bind_incoming_requests_total` from `name` to `opcode` 46 | * [CHANGE] Rename flag `-bind.statsuri` to `-bind.stats-url` 47 | * [CHANGE] Duplicated queries are not an error and get now exported as `bind_query_duplicates_total` 48 | * [FEATURE] Add support for BIND statistics v3 49 | * [FEATURE] Automatically detect BIND statistics version and use correct client 50 | * [FEATURE] Provide option to control exported statistics with `-bind.stats-groups` 51 | * [FEATURE] Export number of queries causing recursion as `bind_query_recursions_total` 52 | * [FEATURE] Export `bind_boot_time_seconds` (v2+v3) and `bind_config_time_seconds` (v3 only) 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bind Exporter 2 | [![GoDoc](https://godoc.org/github.com/prometheus-community/bind_exporter?status.svg)](https://godoc.org/github.com/prometheus-community/bind_exporter) 3 | [![Build Status](https://circleci.com/gh/prometheus-community/bind_exporter.svg?style=svg)](https://circleci.com/gh/prometheus-community/bind_exporter) 4 | [![Go Report Card](https://goreportcard.com/badge/prometheus-community/bind_exporter)](https://goreportcard.com/report/prometheus-community/bind_exporter) 5 | 6 | Export BIND (named/dns) v9+ service metrics to Prometheus. 7 | 8 | ## Getting started 9 | 10 | ### Build and run from source 11 | ```bash 12 | go get github.com/prometheus-community/bind_exporter 13 | cd $GOPATH/src/github.com/prometheus-community/bind_exporter 14 | make 15 | ./bind_exporter [flags] 16 | ``` 17 | 18 | ### Run in Docker container 19 | 20 | 1. Pull Docker container using a specific version: 21 | ```bash 22 | docker pull prometheuscommunity/bind-exporter:v0.3.0 23 | ``` 24 | 2. Run in a Docker container (as daemon), use `--network host` when communicating with `named` via `localhost`: 25 | ```bash 26 | docker run -d --network host prometheuscommunity/bind-exporter:v0.3.0 27 | ``` 28 | 29 | ### Examples 30 | 31 | Run `bind_exporter` in a Docker container and communicate with `named` on non-default statistics URL: 32 | ```bash 33 | docker run -d prometheuscommunity/bind-exporter:v0.3.0 --bind.stats-url http://:8053 34 | ``` 35 | 36 | ## TLS and basic authentication 37 | 38 | The Bind Exporter supports TLS and basic authentication. 39 | 40 | To use TLS and/or basic authentication, you need to pass a configuration file 41 | using the `--web.config.file` parameter. The format of the file is described 42 | [in the exporter-toolkit repository](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md). 43 | 44 | ## Other resources 45 | 46 | [Grafana Dashboard #12309](https://grafana.com/grafana/dashboards/12309) 47 | 48 | ## Troubleshooting 49 | 50 | Make sure BIND was built with libxml2 support. You can check with the following 51 | command: `named -V | grep libxml2`. 52 | 53 | Configure BIND to open a statistics channel. It's recommended to run the 54 | bind\_exporter next to BIND, so it's only necessary to open a port locally. 55 | 56 | ``` 57 | statistics-channels { 58 | inet 127.0.0.1 port 8053 allow { 127.0.0.1; }; 59 | }; 60 | ``` 61 | 62 | --- 63 | 64 | Copyright @ 2016 DigitalOcean™ Inc. 65 | -------------------------------------------------------------------------------- /.github/workflows/container_description.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Push README to Docker Hub 3 | on: 4 | push: 5 | paths: 6 | - "README.md" 7 | - "README-containers.md" 8 | - ".github/workflows/container_description.yml" 9 | branches: [ main, master ] 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | PushDockerHubReadme: 16 | runs-on: ubuntu-latest 17 | name: Push README to Docker Hub 18 | if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. 19 | steps: 20 | - name: git checkout 21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | persist-credentials: false 24 | - name: Set docker hub repo name 25 | run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV 26 | - name: Push README to Dockerhub 27 | uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 28 | env: 29 | DOCKER_USER: ${{ secrets.DOCKER_HUB_LOGIN }} 30 | DOCKER_PASS: ${{ secrets.DOCKER_HUB_PASSWORD }} 31 | with: 32 | destination_container_repo: ${{ env.DOCKER_REPO_NAME }} 33 | provider: dockerhub 34 | short_description: ${{ env.DOCKER_REPO_NAME }} 35 | # Empty string results in README-containers.md being pushed if it 36 | # exists. Otherwise, README.md is pushed. 37 | readme_file: '' 38 | 39 | PushQuayIoReadme: 40 | runs-on: ubuntu-latest 41 | name: Push README to quay.io 42 | if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. 43 | steps: 44 | - name: git checkout 45 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 46 | with: 47 | persist-credentials: false 48 | - name: Set quay.io org name 49 | run: echo "DOCKER_REPO=$(echo quay.io/${GITHUB_REPOSITORY_OWNER} | tr -d '-')" >> $GITHUB_ENV 50 | - name: Set quay.io repo name 51 | run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV 52 | - name: Push README to quay.io 53 | uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 54 | env: 55 | DOCKER_APIKEY: ${{ secrets.QUAY_IO_API_TOKEN }} 56 | with: 57 | destination_container_repo: ${{ env.DOCKER_REPO_NAME }} 58 | provider: quay 59 | # Empty string results in README-containers.md being pushed if it 60 | # exists. Otherwise, README.md is pushed. 61 | readme_file: '' 62 | -------------------------------------------------------------------------------- /bind/bind.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package bind 15 | 16 | import ( 17 | "time" 18 | ) 19 | 20 | // Client queries the BIND API, parses the response and returns stats in a 21 | // generic format. 22 | type Client interface { 23 | Stats(...StatisticGroup) (Statistics, error) 24 | } 25 | 26 | const ( 27 | // QryRTT is the common prefix of query round-trip histogram counters. 28 | QryRTT = "QryRTT" 29 | ) 30 | 31 | // StatisticGroup describes a sub-group of BIND statistics. 32 | type StatisticGroup string 33 | 34 | // Available statistic groups. 35 | const ( 36 | ServerStats StatisticGroup = "server" 37 | ViewStats StatisticGroup = "view" 38 | TaskStats StatisticGroup = "tasks" 39 | ) 40 | 41 | // Statistics is a generic representation of BIND statistics. 42 | type Statistics struct { 43 | Server Server 44 | Views []View 45 | ZoneViews []ZoneView 46 | TaskManager TaskManager 47 | } 48 | 49 | // Server represents BIND server statistics. 50 | type Server struct { 51 | BootTime time.Time 52 | ConfigTime time.Time 53 | IncomingQueries []Counter 54 | IncomingRequests []Counter 55 | NameServerStats []Counter 56 | ZoneStatistics []Counter 57 | ServerRcodes []Counter 58 | } 59 | 60 | // View represents statistics for a single BIND view. 61 | type View struct { 62 | Name string 63 | Cache []Gauge 64 | ResolverStats []Counter 65 | ResolverQueries []Counter 66 | } 67 | 68 | // View represents statistics for a single BIND zone view. 69 | type ZoneView struct { 70 | Name string 71 | ZoneData []ZoneCounter 72 | } 73 | 74 | // TaskManager contains information about all running tasks. 75 | type TaskManager struct { 76 | Tasks []Task `xml:"tasks>task"` 77 | ThreadModel ThreadModel `xml:"thread-model"` 78 | } 79 | 80 | // Counter represents a single counter value. 81 | type Counter struct { 82 | Name string `xml:"name,attr"` 83 | Counter uint64 `xml:",chardata"` 84 | } 85 | 86 | // Counter represents a single zone counter value. 87 | type ZoneCounter struct { 88 | Name string 89 | Serial string 90 | } 91 | 92 | // Gauge represents a single gauge value. 93 | type Gauge struct { 94 | Name string `xml:"name"` 95 | Gauge uint64 `xml:"counter"` 96 | } 97 | 98 | // Task represents a single running task. 99 | type Task struct { 100 | ID string `xml:"id"` 101 | Name string `xml:"name"` 102 | Quantum int64 `xml:"quantum"` 103 | References uint64 `xml:"references"` 104 | State string `xml:"state"` 105 | } 106 | 107 | // ThreadModel contains task and worker information. 108 | type ThreadModel struct { 109 | Type string `xml:"type"` 110 | WorkerThreads uint64 `xml:"worker-threads"` 111 | DefaultQuantum uint64 `xml:"default-quantum"` 112 | TasksRunning uint64 `xml:"tasks-running"` 113 | } 114 | -------------------------------------------------------------------------------- /bind/xml/xml.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package xml 15 | 16 | import ( 17 | "encoding/xml" 18 | "fmt" 19 | "net/http" 20 | "net/url" 21 | "path" 22 | "time" 23 | 24 | "github.com/prometheus-community/bind_exporter/bind" 25 | ) 26 | 27 | const ( 28 | // ServerPath is the HTTP path of the v3 server resource. 29 | ServerPath = "/xml/v3/server" 30 | // StatusPath is the HTTP path of the v3 status resource. 31 | StatusPath = "/xml/v3/status" 32 | // TasksPath is the HTTP path of the v3 tasks resource. 33 | TasksPath = "/xml/v3/tasks" 34 | // ZonesPath is the HTTP path of the v3 zones resource. 35 | ZonesPath = "/xml/v3/zones" 36 | 37 | nsstat = "nsstat" 38 | opcode = "opcode" 39 | qtype = "qtype" 40 | resqtype = "resqtype" 41 | resstats = "resstats" 42 | zonestat = "zonestat" 43 | rcode = "rcode" 44 | ) 45 | 46 | type Statistics struct { 47 | Server Server `xml:"server"` 48 | Taskmgr bind.TaskManager `xml:"taskmgr"` 49 | Views []View `xml:"views>view"` 50 | } 51 | 52 | type ZoneStatistics struct { 53 | ZoneViews []ZoneView `xml:"views>view"` 54 | } 55 | 56 | type Server struct { 57 | BootTime time.Time `xml:"boot-time"` 58 | ConfigTime time.Time `xml:"config-time"` 59 | Counters []Counters `xml:"counters"` 60 | } 61 | 62 | type View struct { 63 | Name string `xml:"name,attr"` 64 | Cache []bind.Gauge `xml:"cache>rrset"` 65 | Counters []Counters `xml:"counters"` 66 | } 67 | 68 | type ZoneView struct { 69 | Name string `xml:"name,attr"` 70 | Zones []ZoneCounter `xml:"zones>zone"` 71 | } 72 | 73 | type Counters struct { 74 | Type string `xml:"type,attr"` 75 | Counters []bind.Counter `xml:"counter"` 76 | } 77 | 78 | type Counter struct { 79 | Name string `xml:"name"` 80 | Counter uint64 `xml:"counter"` 81 | } 82 | 83 | type ZoneCounter struct { 84 | Name string `xml:"name,attr"` 85 | Rdataclass string `xml:"rdataclass,attr"` 86 | Serial string `xml:"serial"` 87 | } 88 | 89 | // Client implements bind.Client and can be used to query a BIND XML v3 API. 90 | type Client struct { 91 | url string 92 | http *http.Client 93 | } 94 | 95 | // NewClient returns an initialized Client. 96 | func NewClient(url string, c *http.Client) *Client { 97 | return &Client{ 98 | url: url, 99 | http: c, 100 | } 101 | } 102 | 103 | // Get queries the given path and stores the result in the value pointed to by 104 | // v. The endpoint must return a valid XML representation which can be 105 | // unmarshaled into the provided value. 106 | func (c *Client) Get(p string, v interface{}) error { 107 | u, err := url.Parse(c.url) 108 | if err != nil { 109 | return fmt.Errorf("invalid URL %q: %s", c.url, err) 110 | } 111 | u.Path = path.Join(u.Path, p) 112 | 113 | resp, err := c.http.Get(u.String()) 114 | if err != nil { 115 | return fmt.Errorf("error querying stats: %s", err) 116 | } 117 | defer resp.Body.Close() 118 | 119 | if resp.StatusCode != http.StatusOK { 120 | return fmt.Errorf("unexpected status %s", resp.Status) 121 | } 122 | 123 | if err := xml.NewDecoder(resp.Body).Decode(v); err != nil { 124 | return fmt.Errorf("failed to unmarshal XML response: %s", err) 125 | } 126 | 127 | return nil 128 | } 129 | 130 | // Stats implements bind.Stats. 131 | func (c *Client) Stats(groups ...bind.StatisticGroup) (bind.Statistics, error) { 132 | s := bind.Statistics{} 133 | m := map[bind.StatisticGroup]bool{} 134 | for _, g := range groups { 135 | m[g] = true 136 | } 137 | 138 | var stats Statistics 139 | var zonestats ZoneStatistics 140 | if m[bind.ServerStats] || m[bind.ViewStats] { 141 | if err := c.Get(ServerPath, &stats); err != nil { 142 | return s, err 143 | } 144 | 145 | s.Server.BootTime = stats.Server.BootTime 146 | s.Server.ConfigTime = stats.Server.ConfigTime 147 | for _, c := range stats.Server.Counters { 148 | switch c.Type { 149 | case opcode: 150 | s.Server.IncomingRequests = c.Counters 151 | case qtype: 152 | s.Server.IncomingQueries = c.Counters 153 | case nsstat: 154 | s.Server.NameServerStats = c.Counters 155 | case zonestat: 156 | s.Server.ZoneStatistics = c.Counters 157 | case rcode: 158 | s.Server.ServerRcodes = c.Counters 159 | } 160 | } 161 | 162 | for _, view := range stats.Views { 163 | v := bind.View{ 164 | Name: view.Name, 165 | Cache: view.Cache, 166 | } 167 | for _, c := range view.Counters { 168 | switch c.Type { 169 | case resqtype: 170 | v.ResolverQueries = c.Counters 171 | case resstats: 172 | v.ResolverStats = c.Counters 173 | } 174 | } 175 | s.Views = append(s.Views, v) 176 | } 177 | } 178 | 179 | if err := c.Get(ZonesPath, &zonestats); err != nil { 180 | return s, err 181 | } 182 | 183 | for _, view := range zonestats.ZoneViews { 184 | v := bind.ZoneView{ 185 | Name: view.Name, 186 | } 187 | for _, zone := range view.Zones { 188 | if zone.Rdataclass != "IN" { 189 | continue 190 | } 191 | z := bind.ZoneCounter{ 192 | Name: zone.Name, 193 | Serial: zone.Serial, 194 | } 195 | v.ZoneData = append(v.ZoneData, z) 196 | } 197 | s.ZoneViews = append(s.ZoneViews, v) 198 | } 199 | 200 | if m[bind.TaskStats] { 201 | if err := c.Get(TasksPath, &stats); err != nil { 202 | return s, err 203 | } 204 | s.TaskManager = stats.Taskmgr 205 | } 206 | 207 | return s, nil 208 | } 209 | -------------------------------------------------------------------------------- /bind/json/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package json 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | "net/http" 20 | "net/url" 21 | "path" 22 | "strconv" 23 | "time" 24 | 25 | "github.com/prometheus-community/bind_exporter/bind" 26 | ) 27 | 28 | const ( 29 | // ServerPath is the HTTP path of the JSON v1 server resource. 30 | ServerPath = "/json/v1/server" 31 | // TasksPath is the HTTP path of the JSON v1 tasks resource. 32 | TasksPath = "/json/v1/tasks" 33 | // ZonesPath is the HTTP path of the JSON v1 zones resource. 34 | ZonesPath = "/json/v1/zones" 35 | ) 36 | 37 | type Gauges map[string]uint64 38 | type Counters map[string]uint64 39 | 40 | type Statistics struct { 41 | BootTime time.Time `json:"boot-time"` 42 | ConfigTime time.Time `json:"config-time"` 43 | Opcodes Counters `json:"opcodes"` 44 | QTypes Counters `json:"qtypes"` 45 | NSStats Counters `json:"nsstats"` 46 | Rcodes Counters `json:"rcodes"` 47 | ZoneStats Counters `json:"zonestats"` 48 | Views map[string]struct { 49 | Resolver struct { 50 | Cache Gauges `json:"cache"` 51 | Qtypes Counters `json:"qtypes"` 52 | Stats Counters `json:"stats"` 53 | } `json:"resolver"` 54 | } `json:"views"` 55 | } 56 | 57 | type ZoneStatistics struct { 58 | Views map[string]struct { 59 | Zones []struct { 60 | Name string `json:"name"` 61 | Class string `json:"class"` 62 | Serial uint32 `json:"serial"` // RFC 1035 specifies SOA serial number as uint32 63 | } `json:"zones"` 64 | } `json:"views"` 65 | } 66 | 67 | type TaskStatistics struct { 68 | TaskMgr struct { 69 | TasksRunning uint64 `json:"tasks-running"` 70 | WorkerThreads uint64 `json:"worker-threads"` 71 | } `json:"taskmgr"` 72 | } 73 | 74 | // Client implements bind.Client and can be used to query a BIND JSON v1 API. 75 | type Client struct { 76 | url string 77 | http *http.Client 78 | } 79 | 80 | // NewClient returns an initialized Client. 81 | func NewClient(url string, c *http.Client) *Client { 82 | return &Client{ 83 | url: url, 84 | http: c, 85 | } 86 | } 87 | 88 | // Get queries the given path and stores the result in the value pointed to by 89 | // v. The endpoint must return a valid JSON representation which can be 90 | // unmarshaled into the provided value. 91 | func (c *Client) Get(p string, v interface{}) error { 92 | u, err := url.Parse(c.url) 93 | if err != nil { 94 | return fmt.Errorf("invalid URL %q: %s", c.url, err) 95 | } 96 | u.Path = path.Join(u.Path, p) 97 | 98 | resp, err := c.http.Get(u.String()) 99 | if err != nil { 100 | return fmt.Errorf("error querying stats: %s", err) 101 | } 102 | defer resp.Body.Close() 103 | 104 | if resp.StatusCode != http.StatusOK { 105 | return fmt.Errorf("unexpected status for %q: %s", u, resp.Status) 106 | } 107 | 108 | if err := json.NewDecoder(resp.Body).Decode(v); err != nil { 109 | return fmt.Errorf("failed to unmarshal JSON response: %s", err) 110 | } 111 | 112 | return nil 113 | } 114 | 115 | // Stats implements bind.Stats. 116 | func (c *Client) Stats(groups ...bind.StatisticGroup) (bind.Statistics, error) { 117 | s := bind.Statistics{} 118 | m := map[bind.StatisticGroup]bool{} 119 | for _, g := range groups { 120 | m[g] = true 121 | } 122 | 123 | if m[bind.ServerStats] || m[bind.ViewStats] { 124 | var stats Statistics 125 | if err := c.Get(ServerPath, &stats); err != nil { 126 | return s, err 127 | } 128 | 129 | s.Server.BootTime = stats.BootTime 130 | s.Server.ConfigTime = stats.ConfigTime 131 | 132 | for k, val := range stats.Opcodes { 133 | s.Server.IncomingRequests = append(s.Server.IncomingRequests, bind.Counter{Name: k, Counter: val}) 134 | } 135 | for k, val := range stats.QTypes { 136 | s.Server.IncomingQueries = append(s.Server.IncomingQueries, bind.Counter{Name: k, Counter: val}) 137 | } 138 | for k, val := range stats.NSStats { 139 | s.Server.NameServerStats = append(s.Server.NameServerStats, bind.Counter{Name: k, Counter: val}) 140 | } 141 | for k, val := range stats.Rcodes { 142 | s.Server.ServerRcodes = append(s.Server.ServerRcodes, bind.Counter{Name: k, Counter: val}) 143 | } 144 | for k, val := range stats.ZoneStats { 145 | s.Server.ZoneStatistics = append(s.Server.ZoneStatistics, bind.Counter{Name: k, Counter: val}) 146 | } 147 | 148 | for name, view := range stats.Views { 149 | v := bind.View{Name: name} 150 | for k, val := range view.Resolver.Cache { 151 | v.Cache = append(v.Cache, bind.Gauge{Name: k, Gauge: val}) 152 | } 153 | for k, val := range view.Resolver.Qtypes { 154 | v.ResolverQueries = append(v.ResolverQueries, bind.Counter{Name: k, Counter: val}) 155 | } 156 | for k, val := range view.Resolver.Stats { 157 | v.ResolverStats = append(v.ResolverStats, bind.Counter{Name: k, Counter: val}) 158 | } 159 | s.Views = append(s.Views, v) 160 | } 161 | } 162 | 163 | var zonestats ZoneStatistics 164 | if err := c.Get(ZonesPath, &zonestats); err != nil { 165 | return s, err 166 | } 167 | 168 | for name, view := range zonestats.Views { 169 | v := bind.ZoneView{ 170 | Name: name, 171 | } 172 | for _, zone := range view.Zones { 173 | if zone.Class != "IN" { 174 | continue 175 | } 176 | z := bind.ZoneCounter{ 177 | Name: zone.Name, 178 | Serial: strconv.FormatUint(uint64(zone.Serial), 10), 179 | } 180 | v.ZoneData = append(v.ZoneData, z) 181 | } 182 | s.ZoneViews = append(s.ZoneViews, v) 183 | } 184 | 185 | if m[bind.TaskStats] { 186 | var taskstats TaskStatistics 187 | if err := c.Get(TasksPath, &taskstats); err != nil { 188 | return s, err 189 | } 190 | s.TaskManager.ThreadModel.TasksRunning = taskstats.TaskMgr.TasksRunning 191 | s.TaskManager.ThreadModel.WorkerThreads = taskstats.TaskMgr.WorkerThreads 192 | } 193 | 194 | return s, nil 195 | } 196 | -------------------------------------------------------------------------------- /bind_exporter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "bytes" 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | "time" 22 | 23 | "github.com/prometheus-community/bind_exporter/bind" 24 | "github.com/prometheus/client_golang/prometheus" 25 | "github.com/prometheus/common/expfmt" 26 | "github.com/prometheus/common/promslog" 27 | ) 28 | 29 | var ( 30 | serverStats = []string{ 31 | `bind_boot_time_seconds 1.626325868e+09`, 32 | `bind_incoming_queries_total{type="A"} 128417`, 33 | `bind_incoming_requests_total{opcode="QUERY"} 37634`, 34 | `bind_responses_total{result="Success"} 29313`, 35 | `bind_query_duplicates_total 216`, 36 | `bind_query_errors_total{error="Dropped"} 237`, 37 | `bind_query_errors_total{error="Failure"} 2950`, 38 | `bind_query_recursions_total 60946`, 39 | `bind_zone_transfer_rejected_total 3`, 40 | `bind_zone_transfer_success_total 25`, 41 | `bind_zone_transfer_failure_total 1`, 42 | `bind_recursive_clients 76`, 43 | `bind_config_time_seconds 1.626325868e+09`, 44 | `bind_response_rcodes_total{rcode="NOERROR"} 989812`, 45 | `bind_response_rcodes_total{rcode="NXDOMAIN"} 33958`, 46 | } 47 | viewStats = []string{ 48 | `bind_resolver_cache_rrsets{type="A",view="_default"} 34324`, 49 | `bind_resolver_queries_total{type="CNAME",view="_default"} 28`, 50 | `bind_resolver_response_errors_total{error="FORMERR",view="_bind"} 0`, 51 | `bind_resolver_response_errors_total{error="FORMERR",view="_default"} 42906`, 52 | `bind_resolver_response_errors_total{error="NXDOMAIN",view="_bind"} 0`, 53 | `bind_resolver_response_errors_total{error="NXDOMAIN",view="_default"} 16707`, 54 | `bind_resolver_response_errors_total{error="OtherError",view="_bind"} 0`, 55 | `bind_resolver_response_errors_total{error="OtherError",view="_default"} 20660`, 56 | `bind_resolver_response_errors_total{error="SERVFAIL",view="_bind"} 0`, 57 | `bind_resolver_response_errors_total{error="SERVFAIL",view="_default"} 7596`, 58 | `bind_resolver_response_lame_total{view="_default"} 9108`, 59 | `bind_resolver_query_duration_seconds_bucket{view="_default",le="0.01"} 38334`, 60 | `bind_resolver_query_duration_seconds_bucket{view="_default",le="0.1"} 113122`, 61 | `bind_resolver_query_duration_seconds_bucket{view="_default",le="0.5"} 182658`, 62 | `bind_resolver_query_duration_seconds_bucket{view="_default",le="0.8"} 187375`, 63 | `bind_resolver_query_duration_seconds_bucket{view="_default",le="1.6"} 188409`, 64 | `bind_resolver_query_duration_seconds_bucket{view="_default",le="+Inf"} 227755`, 65 | `bind_zone_serial{view="_default",zone_name="TEST_ZONE"} 123`, 66 | `bind_resolver_response_errors_total{error="REFUSED",view="_bind"} 17`, 67 | `bind_resolver_response_errors_total{error="REFUSED",view="_default"} 5798`, 68 | } 69 | taskStats = []string{ 70 | `bind_tasks_running 8`, 71 | `bind_worker_threads 16`, 72 | } 73 | ) 74 | 75 | func TestBindExporterJSONClient(t *testing.T) { 76 | bindExporterTest{ 77 | server: newJSONServer(), 78 | groups: []bind.StatisticGroup{bind.ServerStats, bind.ViewStats, bind.TaskStats}, 79 | version: "json", 80 | include: combine([]string{`bind_up 1`}, serverStats, viewStats, taskStats), 81 | }.run(t) 82 | } 83 | 84 | func TestBindExporterV3Client(t *testing.T) { 85 | bindExporterTest{ 86 | server: newV3Server(), 87 | groups: []bind.StatisticGroup{bind.ServerStats, bind.ViewStats, bind.TaskStats}, 88 | version: "xml.v3", 89 | include: combine([]string{`bind_up 1`}, serverStats, viewStats, taskStats), 90 | }.run(t) 91 | } 92 | 93 | func TestBindExporterBindFailure(t *testing.T) { 94 | bindExporterTest{ 95 | server: httptest.NewServer(http.HandlerFunc(http.NotFound)), 96 | version: "xml.v3", 97 | include: []string{`bind_up 0`}, 98 | exclude: serverStats, 99 | }.run(t) 100 | } 101 | 102 | type bindExporterTest struct { 103 | server *httptest.Server 104 | groups []bind.StatisticGroup 105 | version string 106 | include []string 107 | exclude []string 108 | } 109 | 110 | func (b bindExporterTest) run(t *testing.T) { 111 | defer b.server.Close() 112 | 113 | o, err := collect(NewExporter(promslog.NewNopLogger(), b.version, b.server.URL, time.Second, b.groups)) 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | 118 | for _, m := range b.include { 119 | if !bytes.Contains(o, []byte(m)) { 120 | t.Errorf("expected to find metric %q in output\n%s", m, o) 121 | } 122 | } 123 | for _, m := range b.exclude { 124 | if bytes.Contains(o, []byte(m)) { 125 | t.Errorf("expected to not find metric %q in output\n%s", m, o) 126 | } 127 | } 128 | } 129 | 130 | func combine(s ...[]string) []string { 131 | r := []string{} 132 | for _, i := range s { 133 | r = append(r, i...) 134 | } 135 | return r 136 | } 137 | 138 | func collect(c prometheus.Collector) ([]byte, error) { 139 | r := prometheus.NewRegistry() 140 | if err := r.Register(c); err != nil { 141 | return nil, err 142 | } 143 | m, err := r.Gather() 144 | if err != nil { 145 | return nil, err 146 | } 147 | var b bytes.Buffer 148 | enc := expfmt.NewEncoder(&b, expfmt.NewFormat(expfmt.TypeTextPlain)) 149 | for _, f := range m { 150 | if err := enc.Encode(f); err != nil { 151 | return nil, err 152 | } 153 | } 154 | return b.Bytes(), nil 155 | } 156 | 157 | func newV3Server() *httptest.Server { 158 | m := map[string]string{ 159 | "/xml/v3/server": "fixtures/xml/server.xml", 160 | "/xml/v3/status": "fixtures/xml/status.xml", 161 | "/xml/v3/tasks": "fixtures/xml/tasks.xml", 162 | "/xml/v3/zones": "fixtures/xml/zones.xml", 163 | } 164 | return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 165 | if f, ok := m[r.RequestURI]; ok { 166 | http.ServeFile(w, r, f) 167 | } else { 168 | http.NotFound(w, r) 169 | } 170 | })) 171 | } 172 | 173 | func newJSONServer() *httptest.Server { 174 | m := map[string]string{ 175 | "/json/v1/server": "fixtures/json/server.json", 176 | "/json/v1/tasks": "fixtures/json/tasks.json", 177 | "/json/v1/zones": "fixtures/json/zones.json", 178 | } 179 | return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 180 | if f, ok := m[r.RequestURI]; ok { 181 | http.ServeFile(w, r, f) 182 | } else { 183 | http.NotFound(w, r) 184 | } 185 | })) 186 | } 187 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= 2 | github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= 3 | github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= 4 | github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= 5 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 6 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 7 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 8 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= 10 | github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= 15 | github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= 16 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 17 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 18 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 19 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 20 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 21 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 22 | github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= 23 | github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= 24 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 25 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 26 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 27 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 28 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 29 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 30 | github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= 31 | github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= 32 | github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= 33 | github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= 34 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 35 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 36 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= 37 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 38 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 39 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 40 | github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= 41 | github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= 42 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 43 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 44 | github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= 45 | github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= 46 | github.com/prometheus/exporter-toolkit v0.15.0 h1:Pcle5sSViwR1x0gdPd0wtYrPQENBieQAM7TmT0qtb2U= 47 | github.com/prometheus/exporter-toolkit v0.15.0/go.mod h1:OyRWd2iTo6Xge9Kedvv0IhCrJSBu36JCfJ2yVniRIYk= 48 | github.com/prometheus/procfs v0.19.1 h1:QVtROpTkphuXuNlnCv3m1ut3JytkXHtQ3xvck/YmzMM= 49 | github.com/prometheus/procfs v0.19.1/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= 50 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 51 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 52 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 53 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 54 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 55 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 56 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 57 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 58 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 59 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 60 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 61 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 62 | github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= 63 | github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= 64 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 65 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 66 | go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= 67 | go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= 68 | golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= 69 | golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= 70 | golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= 71 | golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= 72 | golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= 73 | golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 74 | golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 75 | golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 76 | golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= 77 | golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 78 | golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= 79 | golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= 80 | golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= 81 | golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 82 | google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= 83 | google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 84 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 85 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 86 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 87 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 88 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 89 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 90 | -------------------------------------------------------------------------------- /Makefile.common: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | 15 | # A common Makefile that includes rules to be reused in different prometheus projects. 16 | # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! 17 | 18 | # Example usage : 19 | # Create the main Makefile in the root project directory. 20 | # include Makefile.common 21 | # customTarget: 22 | # @echo ">> Running customTarget" 23 | # 24 | 25 | # Ensure GOBIN is not set during build so that promu is installed to the correct path 26 | unexport GOBIN 27 | 28 | GO ?= go 29 | GOFMT ?= $(GO)fmt 30 | FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) 31 | GOOPTS ?= 32 | GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) 33 | GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) 34 | 35 | GO_VERSION ?= $(shell $(GO) version) 36 | GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) 37 | PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') 38 | 39 | PROMU := $(FIRST_GOPATH)/bin/promu 40 | pkgs = ./... 41 | 42 | ifeq (arm, $(GOHOSTARCH)) 43 | GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) 44 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) 45 | else 46 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) 47 | endif 48 | 49 | GOTEST := $(GO) test 50 | GOTEST_DIR := 51 | ifneq ($(CIRCLE_JOB),) 52 | ifneq ($(shell command -v gotestsum 2> /dev/null),) 53 | GOTEST_DIR := test-results 54 | GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- 55 | endif 56 | endif 57 | 58 | PROMU_VERSION ?= 0.17.0 59 | PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz 60 | 61 | SKIP_GOLANGCI_LINT := 62 | GOLANGCI_LINT := 63 | GOLANGCI_LINT_OPTS ?= 64 | GOLANGCI_LINT_VERSION ?= v2.6.0 65 | GOLANGCI_FMT_OPTS ?= 66 | # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. 67 | # windows isn't included here because of the path separator being different. 68 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) 69 | ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64)) 70 | # If we're in CI and there is an Actions file, that means the linter 71 | # is being run in Actions, so we don't need to run it here. 72 | ifneq (,$(SKIP_GOLANGCI_LINT)) 73 | GOLANGCI_LINT := 74 | else ifeq (,$(CIRCLE_JOB)) 75 | GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint 76 | else ifeq (,$(wildcard .github/workflows/golangci-lint.yml)) 77 | GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint 78 | endif 79 | endif 80 | endif 81 | 82 | PREFIX ?= $(shell pwd) 83 | BIN_DIR ?= $(shell pwd) 84 | DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) 85 | DOCKERFILE_PATH ?= ./Dockerfile 86 | DOCKERBUILD_CONTEXT ?= ./ 87 | DOCKER_REPO ?= prom 88 | 89 | DOCKER_ARCHS ?= amd64 90 | 91 | BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) 92 | PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) 93 | TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) 94 | 95 | SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG)) 96 | 97 | ifeq ($(GOHOSTARCH),amd64) 98 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) 99 | # Only supported on amd64 100 | test-flags := -race 101 | endif 102 | endif 103 | 104 | # This rule is used to forward a target like "build" to "common-build". This 105 | # allows a new "build" target to be defined in a Makefile which includes this 106 | # one and override "common-build" without override warnings. 107 | %: common-% ; 108 | 109 | .PHONY: common-all 110 | common-all: precheck style check_license lint yamllint unused build test 111 | 112 | .PHONY: common-style 113 | common-style: 114 | @echo ">> checking code style" 115 | @fmtRes=$$($(GOFMT) -d $$(git ls-files '*.go' ':!:vendor/*' || find . -path ./vendor -prune -o -name '*.go' -print)); \ 116 | if [ -n "$${fmtRes}" ]; then \ 117 | echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ 118 | echo "Please ensure you are using $$($(GO) version) for formatting code."; \ 119 | exit 1; \ 120 | fi 121 | 122 | .PHONY: common-check_license 123 | common-check_license: 124 | @echo ">> checking license header" 125 | @licRes=$$(for file in $$(git ls-files '*.go' ':!:vendor/*' || find . -path ./vendor -prune -o -type f -iname '*.go' -print) ; do \ 126 | awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ 127 | done); \ 128 | if [ -n "$${licRes}" ]; then \ 129 | echo "license header checking failed:"; echo "$${licRes}"; \ 130 | exit 1; \ 131 | fi 132 | 133 | .PHONY: common-deps 134 | common-deps: 135 | @echo ">> getting dependencies" 136 | $(GO) mod download 137 | 138 | .PHONY: update-go-deps 139 | update-go-deps: 140 | @echo ">> updating Go dependencies" 141 | @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ 142 | $(GO) get $$m; \ 143 | done 144 | $(GO) mod tidy 145 | 146 | .PHONY: common-test-short 147 | common-test-short: $(GOTEST_DIR) 148 | @echo ">> running short tests" 149 | $(GOTEST) -short $(GOOPTS) $(pkgs) 150 | 151 | .PHONY: common-test 152 | common-test: $(GOTEST_DIR) 153 | @echo ">> running all tests" 154 | $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs) 155 | 156 | $(GOTEST_DIR): 157 | @mkdir -p $@ 158 | 159 | .PHONY: common-format 160 | common-format: $(GOLANGCI_LINT) 161 | @echo ">> formatting code" 162 | $(GO) fmt $(pkgs) 163 | ifdef GOLANGCI_LINT 164 | @echo ">> formatting code with golangci-lint" 165 | $(GOLANGCI_LINT) fmt $(GOLANGCI_FMT_OPTS) 166 | endif 167 | 168 | .PHONY: common-vet 169 | common-vet: 170 | @echo ">> vetting code" 171 | $(GO) vet $(GOOPTS) $(pkgs) 172 | 173 | .PHONY: common-lint 174 | common-lint: $(GOLANGCI_LINT) 175 | ifdef GOLANGCI_LINT 176 | @echo ">> running golangci-lint" 177 | $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) 178 | endif 179 | 180 | .PHONY: common-lint-fix 181 | common-lint-fix: $(GOLANGCI_LINT) 182 | ifdef GOLANGCI_LINT 183 | @echo ">> running golangci-lint fix" 184 | $(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_OPTS) $(pkgs) 185 | endif 186 | 187 | .PHONY: common-yamllint 188 | common-yamllint: 189 | @echo ">> running yamllint on all YAML files in the repository" 190 | ifeq (, $(shell command -v yamllint 2> /dev/null)) 191 | @echo "yamllint not installed so skipping" 192 | else 193 | yamllint . 194 | endif 195 | 196 | # For backward-compatibility. 197 | .PHONY: common-staticcheck 198 | common-staticcheck: lint 199 | 200 | .PHONY: common-unused 201 | common-unused: 202 | @echo ">> running check for unused/missing packages in go.mod" 203 | $(GO) mod tidy 204 | @git diff --exit-code -- go.sum go.mod 205 | 206 | .PHONY: common-build 207 | common-build: promu 208 | @echo ">> building binaries" 209 | $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) 210 | 211 | .PHONY: common-tarball 212 | common-tarball: promu 213 | @echo ">> building release tarball" 214 | $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) 215 | 216 | .PHONY: common-docker-repo-name 217 | common-docker-repo-name: 218 | @echo "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)" 219 | 220 | .PHONY: common-docker $(BUILD_DOCKER_ARCHS) 221 | common-docker: $(BUILD_DOCKER_ARCHS) 222 | $(BUILD_DOCKER_ARCHS): common-docker-%: 223 | docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ 224 | -f $(DOCKERFILE_PATH) \ 225 | --build-arg ARCH="$*" \ 226 | --build-arg OS="linux" \ 227 | $(DOCKERBUILD_CONTEXT) 228 | 229 | .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) 230 | common-docker-publish: $(PUBLISH_DOCKER_ARCHS) 231 | $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: 232 | docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" 233 | 234 | DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) 235 | .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) 236 | common-docker-tag-latest: $(TAG_DOCKER_ARCHS) 237 | $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: 238 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" 239 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" 240 | 241 | .PHONY: common-docker-manifest 242 | common-docker-manifest: 243 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)) 244 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" 245 | 246 | .PHONY: promu 247 | promu: $(PROMU) 248 | 249 | $(PROMU): 250 | $(eval PROMU_TMP := $(shell mktemp -d)) 251 | curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) 252 | mkdir -p $(FIRST_GOPATH)/bin 253 | cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu 254 | rm -r $(PROMU_TMP) 255 | 256 | .PHONY: common-proto 257 | common-proto: 258 | @echo ">> generating code from proto files" 259 | @./scripts/genproto.sh 260 | 261 | ifdef GOLANGCI_LINT 262 | $(GOLANGCI_LINT): 263 | mkdir -p $(FIRST_GOPATH)/bin 264 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ 265 | | sed -e '/install -d/d' \ 266 | | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) 267 | endif 268 | 269 | .PHONY: common-print-golangci-lint-version 270 | common-print-golangci-lint-version: 271 | @echo $(GOLANGCI_LINT_VERSION) 272 | 273 | .PHONY: precheck 274 | precheck:: 275 | 276 | define PRECHECK_COMMAND_template = 277 | precheck:: $(1)_precheck 278 | 279 | PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) 280 | .PHONY: $(1)_precheck 281 | $(1)_precheck: 282 | @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ 283 | echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ 284 | exit 1; \ 285 | fi 286 | endef 287 | 288 | govulncheck: install-govulncheck 289 | govulncheck ./... 290 | 291 | install-govulncheck: 292 | command -v govulncheck > /dev/null || go install golang.org/x/vuln/cmd/govulncheck@latest 293 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /bind_exporter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "log/slog" 19 | "math" 20 | "net/http" 21 | _ "net/http/pprof" 22 | "os" 23 | "sort" 24 | "strconv" 25 | "strings" 26 | "time" 27 | 28 | "github.com/alecthomas/kingpin/v2" 29 | "github.com/prometheus-community/bind_exporter/bind" 30 | "github.com/prometheus-community/bind_exporter/bind/json" 31 | "github.com/prometheus-community/bind_exporter/bind/xml" 32 | "github.com/prometheus/client_golang/prometheus" 33 | "github.com/prometheus/client_golang/prometheus/collectors" 34 | clientVersion "github.com/prometheus/client_golang/prometheus/collectors/version" 35 | "github.com/prometheus/client_golang/prometheus/promhttp" 36 | "github.com/prometheus/common/promslog" 37 | "github.com/prometheus/common/promslog/flag" 38 | "github.com/prometheus/common/version" 39 | "github.com/prometheus/exporter-toolkit/web" 40 | webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag" 41 | ) 42 | 43 | const ( 44 | namespace = "bind" 45 | exporter = "bind_exporter" 46 | resolver = "resolver" 47 | ) 48 | 49 | var ( 50 | up = prometheus.NewDesc( 51 | prometheus.BuildFQName(namespace, "", "up"), 52 | "Was the Bind instance query successful?", 53 | nil, nil, 54 | ) 55 | bootTime = prometheus.NewDesc( 56 | prometheus.BuildFQName(namespace, "", "boot_time_seconds"), 57 | "Start time of the BIND process since unix epoch in seconds.", 58 | nil, nil, 59 | ) 60 | configTime = prometheus.NewDesc( 61 | prometheus.BuildFQName(namespace, "", "config_time_seconds"), 62 | "Time of the last reconfiguration since unix epoch in seconds.", 63 | nil, nil, 64 | ) 65 | incomingQueries = prometheus.NewDesc( 66 | prometheus.BuildFQName(namespace, "", "incoming_queries_total"), 67 | "Number of incoming DNS queries.", 68 | []string{"type"}, nil, 69 | ) 70 | incomingRequests = prometheus.NewDesc( 71 | prometheus.BuildFQName(namespace, "", "incoming_requests_total"), 72 | "Number of incoming DNS requests.", 73 | []string{"opcode"}, nil, 74 | ) 75 | resolverCache = prometheus.NewDesc( 76 | prometheus.BuildFQName(namespace, resolver, "cache_rrsets"), 77 | "Number of RRSets in Cache database.", 78 | []string{"view", "type"}, nil, 79 | ) 80 | resolverQueries = prometheus.NewDesc( 81 | prometheus.BuildFQName(namespace, resolver, "queries_total"), 82 | "Number of outgoing DNS queries.", 83 | []string{"view", "type"}, nil, 84 | ) 85 | resolverQueryDuration = prometheus.NewDesc( 86 | prometheus.BuildFQName(namespace, resolver, "query_duration_seconds"), 87 | "Resolver query round-trip time in seconds.", 88 | []string{"view"}, nil, 89 | ) 90 | resolverQueryErrors = prometheus.NewDesc( 91 | prometheus.BuildFQName(namespace, resolver, "query_errors_total"), 92 | "Number of resolver queries failed.", 93 | []string{"view", "error"}, nil, 94 | ) 95 | resolverResponseErrors = prometheus.NewDesc( 96 | prometheus.BuildFQName(namespace, resolver, "response_errors_total"), 97 | "Number of resolver response errors received.", 98 | []string{"view", "error"}, nil, 99 | ) 100 | resolverDNSSECSuccess = prometheus.NewDesc( 101 | prometheus.BuildFQName(namespace, resolver, "dnssec_validation_success_total"), 102 | "Number of DNSSEC validation attempts succeeded.", 103 | []string{"view", "result"}, nil, 104 | ) 105 | resolverMetricStats = map[string]*prometheus.Desc{ 106 | "Lame": prometheus.NewDesc( 107 | prometheus.BuildFQName(namespace, resolver, "response_lame_total"), 108 | "Number of lame delegation responses received.", 109 | []string{"view"}, nil, 110 | ), 111 | "EDNS0Fail": prometheus.NewDesc( 112 | prometheus.BuildFQName(namespace, resolver, "query_edns0_errors_total"), 113 | "Number of EDNS(0) query errors.", 114 | []string{"view"}, nil, 115 | ), 116 | "Mismatch": prometheus.NewDesc( 117 | prometheus.BuildFQName(namespace, resolver, "response_mismatch_total"), 118 | "Number of mismatch responses received.", 119 | []string{"view"}, nil, 120 | ), 121 | "Retry": prometheus.NewDesc( 122 | prometheus.BuildFQName(namespace, resolver, "query_retries_total"), 123 | "Number of resolver query retries.", 124 | []string{"view"}, nil, 125 | ), 126 | "Truncated": prometheus.NewDesc( 127 | prometheus.BuildFQName(namespace, resolver, "response_truncated_total"), 128 | "Number of truncated responses received.", 129 | []string{"view"}, nil, 130 | ), 131 | "ValFail": prometheus.NewDesc( 132 | prometheus.BuildFQName(namespace, resolver, "dnssec_validation_errors_total"), 133 | "Number of DNSSEC validation attempt errors.", 134 | []string{"view"}, nil, 135 | ), 136 | } 137 | resolverLabelStats = map[string]*prometheus.Desc{ 138 | "QueryAbort": resolverQueryErrors, 139 | "QuerySockFail": resolverQueryErrors, 140 | "QueryTimeout": resolverQueryErrors, 141 | "NXDOMAIN": resolverResponseErrors, 142 | "SERVFAIL": resolverResponseErrors, 143 | "FORMERR": resolverResponseErrors, 144 | "OtherError": resolverResponseErrors, 145 | "REFUSED": resolverResponseErrors, 146 | "ValOk": resolverDNSSECSuccess, 147 | "ValNegOk": resolverDNSSECSuccess, 148 | } 149 | serverQueryErrors = prometheus.NewDesc( 150 | prometheus.BuildFQName(namespace, "", "query_errors_total"), 151 | "Number of query failures.", 152 | []string{"error"}, nil, 153 | ) 154 | serverResponses = prometheus.NewDesc( 155 | prometheus.BuildFQName(namespace, "", "responses_total"), 156 | "Number of responses sent.", 157 | []string{"result"}, nil, 158 | ) 159 | serverLabelStats = map[string]*prometheus.Desc{ 160 | "QryDropped": serverQueryErrors, 161 | "QryFailure": serverQueryErrors, 162 | "QrySuccess": serverResponses, 163 | "QryReferral": serverResponses, 164 | "QryNxrrset": serverResponses, 165 | "QrySERVFAIL": serverResponses, 166 | "QryFORMERR": serverResponses, 167 | "QryNXDOMAIN": serverResponses, 168 | } 169 | serverRcodes = prometheus.NewDesc( 170 | prometheus.BuildFQName(namespace, "", "response_rcodes_total"), 171 | "Number of responses sent per RCODE.", 172 | []string{"rcode"}, nil, 173 | ) 174 | serverMetricStats = map[string]*prometheus.Desc{ 175 | "QryDuplicate": prometheus.NewDesc( 176 | prometheus.BuildFQName(namespace, "", "query_duplicates_total"), 177 | "Number of duplicated queries received.", 178 | nil, nil, 179 | ), 180 | "QryRecursion": prometheus.NewDesc( 181 | prometheus.BuildFQName(namespace, "", "query_recursions_total"), 182 | "Number of queries causing recursion.", 183 | nil, nil, 184 | ), 185 | "XfrRej": prometheus.NewDesc( 186 | prometheus.BuildFQName(namespace, "", "zone_transfer_rejected_total"), 187 | "Number of rejected zone transfers.", 188 | nil, nil, 189 | ), 190 | "XfrSuccess": prometheus.NewDesc( 191 | prometheus.BuildFQName(namespace, "", "zone_transfer_success_total"), 192 | "Number of successful zone transfers.", 193 | nil, nil, 194 | ), 195 | "XfrFail": prometheus.NewDesc( 196 | prometheus.BuildFQName(namespace, "", "zone_transfer_failure_total"), 197 | "Number of failed zone transfers.", 198 | nil, nil, 199 | ), 200 | "RecursClients": prometheus.NewDesc( 201 | prometheus.BuildFQName(namespace, "", "recursive_clients"), 202 | "Number of current recursive clients.", 203 | nil, nil, 204 | ), 205 | "RPZRewrites": prometheus.NewDesc( 206 | prometheus.BuildFQName(namespace, "", "response_policy_zone_rewrites_total"), 207 | "Number of response policy zone rewrites.", 208 | nil, nil, 209 | ), 210 | } 211 | tasksRunning = prometheus.NewDesc( 212 | prometheus.BuildFQName(namespace, "", "tasks_running"), 213 | "Number of running tasks.", 214 | nil, nil, 215 | ) 216 | workerThreads = prometheus.NewDesc( 217 | prometheus.BuildFQName(namespace, "", "worker_threads"), 218 | "Total number of available worker threads.", 219 | nil, nil, 220 | ) 221 | zoneSerial = prometheus.NewDesc( 222 | prometheus.BuildFQName(namespace, "", "zone_serial"), 223 | "Zone serial number.", 224 | []string{"view", "zone_name"}, nil, 225 | ) 226 | ) 227 | 228 | type collectorConstructor func(*slog.Logger, *bind.Statistics) prometheus.Collector 229 | 230 | type serverCollector struct { 231 | logger *slog.Logger 232 | stats *bind.Statistics 233 | } 234 | 235 | // newServerCollector implements collectorConstructor. 236 | func newServerCollector(logger *slog.Logger, s *bind.Statistics) prometheus.Collector { 237 | return &serverCollector{logger: logger, stats: s} 238 | } 239 | 240 | // Describe implements prometheus.Collector. 241 | func (c *serverCollector) Describe(ch chan<- *prometheus.Desc) { 242 | ch <- bootTime 243 | ch <- configTime 244 | ch <- incomingQueries 245 | ch <- incomingRequests 246 | ch <- serverQueryErrors 247 | ch <- serverResponses 248 | ch <- serverRcodes 249 | for _, desc := range serverMetricStats { 250 | ch <- desc 251 | } 252 | } 253 | 254 | // Collect implements prometheus.Collector. 255 | func (c *serverCollector) Collect(ch chan<- prometheus.Metric) { 256 | ch <- prometheus.MustNewConstMetric( 257 | bootTime, prometheus.GaugeValue, float64(c.stats.Server.BootTime.Unix()), 258 | ) 259 | if !c.stats.Server.ConfigTime.IsZero() { 260 | ch <- prometheus.MustNewConstMetric( 261 | configTime, prometheus.GaugeValue, float64(c.stats.Server.ConfigTime.Unix()), 262 | ) 263 | } 264 | for _, s := range c.stats.Server.IncomingQueries { 265 | ch <- prometheus.MustNewConstMetric( 266 | incomingQueries, prometheus.CounterValue, float64(s.Counter), s.Name, 267 | ) 268 | } 269 | for _, s := range c.stats.Server.IncomingRequests { 270 | ch <- prometheus.MustNewConstMetric( 271 | incomingRequests, prometheus.CounterValue, float64(s.Counter), s.Name, 272 | ) 273 | } 274 | for _, s := range c.stats.Server.NameServerStats { 275 | if desc, ok := serverLabelStats[s.Name]; ok { 276 | r := strings.TrimPrefix(s.Name, "Qry") 277 | ch <- prometheus.MustNewConstMetric( 278 | desc, prometheus.CounterValue, float64(s.Counter), r, 279 | ) 280 | } 281 | if desc, ok := serverMetricStats[s.Name]; ok { 282 | ch <- prometheus.MustNewConstMetric( 283 | desc, prometheus.CounterValue, float64(s.Counter), 284 | ) 285 | } 286 | } 287 | for _, s := range c.stats.Server.ServerRcodes { 288 | ch <- prometheus.MustNewConstMetric( 289 | serverRcodes, prometheus.CounterValue, float64(s.Counter), s.Name, 290 | ) 291 | } 292 | for _, s := range c.stats.Server.ZoneStatistics { 293 | if desc, ok := serverMetricStats[s.Name]; ok { 294 | ch <- prometheus.MustNewConstMetric( 295 | desc, prometheus.CounterValue, float64(s.Counter), 296 | ) 297 | } 298 | } 299 | } 300 | 301 | type viewCollector struct { 302 | logger *slog.Logger 303 | stats *bind.Statistics 304 | } 305 | 306 | // newViewCollector implements collectorConstructor. 307 | func newViewCollector(logger *slog.Logger, s *bind.Statistics) prometheus.Collector { 308 | return &viewCollector{logger: logger, stats: s} 309 | } 310 | 311 | // Describe implements prometheus.Collector. 312 | func (c *viewCollector) Describe(ch chan<- *prometheus.Desc) { 313 | ch <- resolverDNSSECSuccess 314 | ch <- resolverQueries 315 | ch <- resolverQueryDuration 316 | ch <- resolverQueryErrors 317 | ch <- resolverResponseErrors 318 | for _, desc := range resolverMetricStats { 319 | ch <- desc 320 | } 321 | } 322 | 323 | // Collect implements prometheus.Collector. 324 | func (c *viewCollector) Collect(ch chan<- prometheus.Metric) { 325 | for _, v := range c.stats.Views { 326 | for _, s := range v.Cache { 327 | ch <- prometheus.MustNewConstMetric( 328 | resolverCache, prometheus.GaugeValue, float64(s.Gauge), v.Name, s.Name, 329 | ) 330 | } 331 | for _, s := range v.ResolverQueries { 332 | ch <- prometheus.MustNewConstMetric( 333 | resolverQueries, prometheus.CounterValue, float64(s.Counter), v.Name, s.Name, 334 | ) 335 | } 336 | for _, s := range v.ResolverStats { 337 | if desc, ok := resolverMetricStats[s.Name]; ok { 338 | ch <- prometheus.MustNewConstMetric( 339 | desc, prometheus.CounterValue, float64(s.Counter), v.Name, 340 | ) 341 | } 342 | if desc, ok := resolverLabelStats[s.Name]; ok { 343 | ch <- prometheus.MustNewConstMetric( 344 | desc, prometheus.CounterValue, float64(s.Counter), v.Name, s.Name, 345 | ) 346 | } 347 | } 348 | if buckets, count, err := histogram(v.ResolverStats); err == nil { 349 | ch <- prometheus.MustNewConstHistogram( 350 | resolverQueryDuration, count, math.NaN(), buckets, v.Name, 351 | ) 352 | } else { 353 | c.logger.Warn("Error parsing RTT", "err", err) 354 | } 355 | } 356 | 357 | for _, v := range c.stats.ZoneViews { 358 | for _, z := range v.ZoneData { 359 | if suint, err := strconv.ParseUint(z.Serial, 10, 64); err == nil { 360 | ch <- prometheus.MustNewConstMetric( 361 | zoneSerial, prometheus.CounterValue, float64(suint), v.Name, z.Name, 362 | ) 363 | } 364 | } 365 | } 366 | } 367 | 368 | type taskCollector struct { 369 | logger *slog.Logger 370 | stats *bind.Statistics 371 | } 372 | 373 | // newTaskCollector implements collectorConstructor. 374 | func newTaskCollector(logger *slog.Logger, s *bind.Statistics) prometheus.Collector { 375 | return &taskCollector{logger: logger, stats: s} 376 | } 377 | 378 | // Describe implements prometheus.Collector. 379 | func (c *taskCollector) Describe(ch chan<- *prometheus.Desc) { 380 | ch <- tasksRunning 381 | ch <- workerThreads 382 | } 383 | 384 | // Collect implements prometheus.Collector. 385 | func (c *taskCollector) Collect(ch chan<- prometheus.Metric) { 386 | threadModel := c.stats.TaskManager.ThreadModel 387 | ch <- prometheus.MustNewConstMetric( 388 | tasksRunning, prometheus.GaugeValue, float64(threadModel.TasksRunning), 389 | ) 390 | ch <- prometheus.MustNewConstMetric( 391 | workerThreads, prometheus.GaugeValue, float64(threadModel.WorkerThreads), 392 | ) 393 | } 394 | 395 | // Exporter collects Binds stats from the given server and exports them using 396 | // the prometheus metrics package. 397 | type Exporter struct { 398 | client bind.Client 399 | collectors []collectorConstructor 400 | groups []bind.StatisticGroup 401 | logger *slog.Logger 402 | } 403 | 404 | // NewExporter returns an initialized Exporter. 405 | func NewExporter(logger *slog.Logger, version, url string, timeout time.Duration, g []bind.StatisticGroup) *Exporter { 406 | var c bind.Client 407 | switch version { 408 | case "xml", "xml.v3": 409 | c = xml.NewClient(url, &http.Client{Timeout: timeout}) 410 | default: 411 | c = json.NewClient(url, &http.Client{Timeout: timeout}) 412 | } 413 | 414 | var cs []collectorConstructor 415 | for _, g := range g { 416 | switch g { 417 | case bind.ServerStats: 418 | cs = append(cs, newServerCollector) 419 | case bind.ViewStats: 420 | cs = append(cs, newViewCollector) 421 | case bind.TaskStats: 422 | cs = append(cs, newTaskCollector) 423 | } 424 | } 425 | 426 | return &Exporter{logger: logger, client: c, collectors: cs, groups: g} 427 | } 428 | 429 | // Describe describes all the metrics ever exported by the bind exporter. It 430 | // implements prometheus.Collector. 431 | func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { 432 | ch <- up 433 | for _, c := range e.collectors { 434 | c(e.logger, &bind.Statistics{}).Describe(ch) 435 | } 436 | } 437 | 438 | // Collect fetches the stats from configured bind location and delivers them as 439 | // Prometheus metrics. It implements prometheus.Collector. 440 | func (e *Exporter) Collect(ch chan<- prometheus.Metric) { 441 | status := 0. 442 | if stats, err := e.client.Stats(e.groups...); err == nil { 443 | for _, c := range e.collectors { 444 | c(e.logger, &stats).Collect(ch) 445 | } 446 | status = 1 447 | } else { 448 | e.logger.Error("Couldn't retrieve BIND stats", "err", err) 449 | } 450 | ch <- prometheus.MustNewConstMetric(up, prometheus.GaugeValue, status) 451 | } 452 | 453 | func histogram(stats []bind.Counter) (map[float64]uint64, uint64, error) { 454 | buckets := map[float64]uint64{} 455 | var count uint64 456 | 457 | for _, s := range stats { 458 | if strings.HasPrefix(s.Name, bind.QryRTT) { 459 | b := math.Inf(0) 460 | if !strings.HasSuffix(s.Name, "+") { 461 | var err error 462 | rrt := strings.TrimPrefix(s.Name, bind.QryRTT) 463 | b, err = strconv.ParseFloat(rrt, 32) 464 | if err != nil { 465 | return buckets, 0, fmt.Errorf("could not parse RTT: %s", rrt) 466 | } 467 | } 468 | 469 | buckets[b/1000] = s.Counter 470 | } 471 | } 472 | 473 | // Don't assume that QryRTT counters were in ascending order before summing them. 474 | // JSON stats are unmarshaled into a map, which won't preserve the order that BIND renders. 475 | keys := make([]float64, 0, len(buckets)) 476 | for k := range buckets { 477 | keys = append(keys, k) 478 | } 479 | sort.Float64s(keys) 480 | 481 | for _, k := range keys { 482 | buckets[k] += count 483 | count = buckets[k] 484 | } 485 | 486 | return buckets, count, nil 487 | } 488 | 489 | type statisticGroups []bind.StatisticGroup 490 | 491 | // String implements flag.Value. 492 | func (s *statisticGroups) String() string { 493 | groups := []string{} 494 | for _, g := range *s { 495 | groups = append(groups, string(g)) 496 | } 497 | return strings.Join(groups, ",") 498 | } 499 | 500 | // Set implements flag.Value. 501 | func (s *statisticGroups) Set(value string) error { 502 | *s = []bind.StatisticGroup{} 503 | if len(value) == 0 { 504 | return nil 505 | } 506 | var sg bind.StatisticGroup 507 | for _, dt := range strings.Split(value, ",") { 508 | switch dt { 509 | case string(bind.ServerStats): 510 | sg = bind.ServerStats 511 | case string(bind.ViewStats): 512 | sg = bind.ViewStats 513 | case string(bind.TaskStats): 514 | sg = bind.TaskStats 515 | default: 516 | return fmt.Errorf("unknown stats group %q", dt) 517 | } 518 | for _, existing := range *s { 519 | if existing == sg { 520 | return fmt.Errorf("duplicated stats group %q", sg) 521 | } 522 | } 523 | *s = append(*s, sg) 524 | } 525 | return nil 526 | } 527 | 528 | func main() { 529 | var ( 530 | bindURI = kingpin.Flag("bind.stats-url", 531 | "HTTP XML API address of BIND server", 532 | ).Default("http://localhost:8053/").String() 533 | bindTimeout = kingpin.Flag("bind.timeout", 534 | "Timeout for trying to get stats from BIND server", 535 | ).Default("10s").Duration() 536 | bindPidFile = kingpin.Flag("bind.pid-file", 537 | "Path to BIND's pid file to export process information", 538 | ).Default("/run/named/named.pid").String() 539 | bindVersion = kingpin.Flag("bind.stats-version", 540 | "BIND statistics channel", 541 | ).Default("json").Enum("json", "xml", "xml.v3", "auto") 542 | metricsPath = kingpin.Flag( 543 | "web.telemetry-path", "Path under which to expose metrics", 544 | ).Default("/metrics").String() 545 | 546 | groups statisticGroups 547 | ) 548 | 549 | toolkitFlags := webflag.AddFlags(kingpin.CommandLine, ":9119") 550 | 551 | kingpin.Flag("bind.stats-groups", 552 | "Comma-separated list of statistics to collect", 553 | ).Default((&statisticGroups{ 554 | bind.ServerStats, bind.ViewStats, 555 | }).String()).SetValue(&groups) 556 | 557 | promslogConfig := &promslog.Config{} 558 | flag.AddFlags(kingpin.CommandLine, promslogConfig) 559 | kingpin.Version(version.Print(exporter)) 560 | kingpin.HelpFlag.Short('h') 561 | kingpin.Parse() 562 | logger := promslog.New(promslogConfig) 563 | 564 | logger.Info("Starting bind_exporter", "version", version.Info()) 565 | logger.Info("Build context", "build_context", version.BuildContext()) 566 | logger.Info("Collectors enabled", "collectors", groups.String()) 567 | 568 | prometheus.MustRegister( 569 | clientVersion.NewCollector(exporter), 570 | NewExporter(logger, *bindVersion, *bindURI, *bindTimeout, groups), 571 | ) 572 | if *bindPidFile != "" { 573 | procExporter := collectors.NewProcessCollector(collectors.ProcessCollectorOpts{ 574 | PidFn: prometheus.NewPidFileFn(*bindPidFile), 575 | Namespace: namespace, 576 | }) 577 | prometheus.MustRegister(procExporter) 578 | } 579 | 580 | http.Handle(*metricsPath, promhttp.Handler()) 581 | if *metricsPath != "/" && *metricsPath != "" { 582 | landingConfig := web.LandingConfig{ 583 | Name: "Bind Exporter", 584 | Description: "Prometheus Exporter for BIND DNS servers", 585 | Version: version.Info(), 586 | Links: []web.LandingLinks{ 587 | { 588 | Address: *metricsPath, 589 | Text: "Metrics", 590 | }, 591 | }, 592 | } 593 | landingPage, err := web.NewLandingPage(landingConfig) 594 | if err != nil { 595 | logger.Error("Error creating landing page", "err", err) 596 | os.Exit(1) 597 | } 598 | http.Handle("/", landingPage) 599 | } 600 | 601 | srv := &http.Server{} 602 | if err := web.ListenAndServe(srv, toolkitFlags, logger); err != nil { 603 | logger.Error("Error starting HTTP server", "err", err) 604 | os.Exit(1) 605 | } 606 | } 607 | -------------------------------------------------------------------------------- /fixtures/xml/server.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 2021-07-15T05:11:08.926Z 6 | 2021-07-15T05:11:08.972Z 7 | 2021-07-15T10:25:39.396Z 8 | 9.11.31 9 | 10 | 37634 11 | 0 12 | 0 13 | 0 14 | 0 15 | 0 16 | 0 17 | 0 18 | 0 19 | 0 20 | 0 21 | 0 22 | 0 23 | 0 24 | 0 25 | 0 26 | 27 | 28 | 989812 29 | 0 30 | 135 31 | 33958 32 | 0 33 | 123 34 | 0 35 | 0 36 | 0 37 | 0 38 | 0 39 | 0 40 | 0 41 | 0 42 | 0 43 | 0 44 | 0 45 | 0 46 | 0 47 | 0 48 | 0 49 | 0 50 | 0 51 | 0 52 | 53 | 54 | 128417 55 | 1 56 | 57 | 58 | 156 59 | 0 60 | 4 61 | 0 62 | 0 63 | 0 64 | 0 65 | 0 66 | 0 67 | 0 68 | 3 69 | 0 70 | 156 71 | 0 72 | 4 73 | 0 74 | 0 75 | 29313 76 | 0 77 | 6 78 | 0 79 | 0 80 | 150 81 | 0 82 | 1 83 | 60946 84 | 216 85 | 237 86 | 2950 87 | 0 88 | 0 89 | 0 90 | 0 91 | 0 92 | 0 93 | 0 94 | 76 95 | 0 96 | 0 97 | 0 98 | 0 99 | 156 100 | 0 101 | 0 102 | 0 103 | 0 104 | 0 105 | 0 106 | 0 107 | 0 108 | 0 109 | 0 110 | 0 111 | 0 112 | 0 113 | 0 114 | 0 115 | 0 116 | 117 | 118 | 0 119 | 0 120 | 0 121 | 0 122 | 0 123 | 0 124 | 0 125 | 0 126 | 0 127 | 0 128 | 0 129 | 25 130 | 1 131 | 132 | 133 | 134 | 30 135 | 241 136 | 8 137 | 237 138 | 0 139 | 1 140 | 0 141 | 0 142 | 0 143 | 0 144 | 0 145 | 0 146 | 0 147 | 236 148 | 8181 149 | 1057 150 | 0 151 | 0 152 | 0 153 | 0 154 | 0 155 | 0 156 | 0 157 | 0 158 | 0 159 | 0 160 | 0 161 | 0 162 | 0 163 | 0 164 | 0 165 | 0 166 | 0 167 | 0 168 | 236 169 | 0 170 | 0 171 | 0 172 | 0 173 | 0 174 | 8183 175 | 821 176 | 0 177 | 0 178 | 0 179 | 0 180 | 0 181 | 0 182 | 0 183 | 0 184 | 0 185 | 1 186 | 0 187 | 0 188 | 0 189 | 0 190 | 30 191 | 5 192 | 10 193 | 1 194 | 0 195 | 1 196 | 197 | 198 | 199 | 200 | 201 | 202 | 16508 203 | 698006 204 | 172395 205 | 34208 206 | 3964 207 | 200 208 | 11 209 | 3 210 | 5 211 | 212 | 213 | 489 214 | 4844 215 | 1123 216 | 3028 217 | 1079 218 | 787 219 | 1218 220 | 1068 221 | 1627 222 | 598 223 | 712 224 | 521 225 | 1617 226 | 404 227 | 469 228 | 197 229 | 403 230 | 3252 231 | 6245 232 | 3133 233 | 2355 234 | 1076 235 | 2391 236 | 5384 237 | 6393 238 | 2167 239 | 932 240 | 4067 241 | 3183 242 | 3039 243 | 5734 244 | 3823 245 | 506 246 | 24 247 | 696 248 | 16043 249 | 58674 250 | 51954 251 | 40426 252 | 39055 253 | 44204 254 | 55469 255 | 52930 256 | 36804 257 | 21366 258 | 95769 259 | 233268 260 | 17125 261 | 21844 262 | 8868 263 | 7589 264 | 797 265 | 590 266 | 24 267 | 14492 268 | 1 269 | 709 270 | 58 271 | 97 272 | 40 273 | 74 274 | 17269 275 | 13792 276 | 1096 277 | 278 278 | 8 279 | 1 280 | 1 281 | 1 282 | 283 | 284 | 285 | 286 | 461 287 | 4370 288 | 619 289 | 2867 290 | 203 291 | 6 292 | 293 | 294 | 6 295 | 8 296 | 70 297 | 9 298 | 1 299 | 20 300 | 3 301 | 21 302 | 2 303 | 1 304 | 66 305 | 218 306 | 474 307 | 647 308 | 627 309 | 526 310 | 623 311 | 1174 312 | 959 313 | 469 314 | 351 315 | 321 316 | 480 317 | 157 318 | 256 319 | 214 320 | 58 321 | 54 322 | 425 323 | 100 324 | 6 325 | 1 326 | 6 327 | 83 328 | 60 329 | 21 330 | 1 331 | 8 332 | 333 | 334 | 335 | 336 | 337 | 338 | 1110 339 | 56243 340 | 7827 341 | 21910 342 | 1988 343 | 61 344 | 5 345 | 1 346 | 236 347 | 348 | 349 | 14 350 | 236 351 | 102 352 | 457 353 | 23 354 | 284 355 | 1 356 | 5 357 | 2 358 | 1 359 | 17 360 | 7 361 | 44 362 | 865 363 | 443 364 | 593 365 | 454 366 | 258 367 | 766 368 | 793 369 | 845 370 | 229 371 | 273 372 | 733 373 | 234 374 | 917 375 | 1455 376 | 875 377 | 41 378 | 12 379 | 82 380 | 1599 381 | 5702 382 | 5595 383 | 6738 384 | 5446 385 | 6034 386 | 7267 387 | 7008 388 | 5964 389 | 2678 390 | 4234 391 | 7560 392 | 2625 393 | 3018 394 | 1589 395 | 1160 396 | 1291 397 | 91 398 | 8 399 | 1109 400 | 1 401 | 243 402 | 5 403 | 4 404 | 1 405 | 22 406 | 642 407 | 275 408 | 340 409 | 69 410 | 1 411 | 1 412 | 413 | 414 | 415 | 416 | 12 417 | 236 418 | 79 419 | 480 420 | 14 421 | 422 | 423 | 1 424 | 10 425 | 28 426 | 75 427 | 58 428 | 62 429 | 80 430 | 91 431 | 72 432 | 42 433 | 35 434 | 45 435 | 47 436 | 12 437 | 29 438 | 40 439 | 34 440 | 1 441 | 13 442 | 26 443 | 1 444 | 8 445 | 9 446 | 2 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 1514 455 | 53 456 | 376 457 | 28 458 | 459 | 460 | 1574 461 | 369 462 | 146 463 | 0 464 | 16707 465 | 7596 466 | 42906 467 | 20660 468 | 0 469 | 0 470 | 35 471 | 9108 472 | 1686 473 | 0 474 | 0 475 | 0 476 | 0 477 | 9 478 | 24 479 | 35 480 | 0 481 | 22 482 | 0 483 | 0 484 | 0 485 | 0 486 | 38334 487 | 74788 488 | 69536 489 | 4717 490 | 1034 491 | 39346 492 | 0 493 | 31 494 | 5798 495 | 0 496 | 0 497 | 0 498 | 0 499 | 0 500 | 0 501 | 0 502 | 0 503 | 0 504 | 505 | 506 | 507 | A 508 | 34324 509 | 510 | 511 | NS 512 | 11 513 | 514 | 515 | CNAME 516 | 1 517 | 518 | 519 | AAAA 520 | 4 521 | 522 | 523 | DS 524 | 3 525 | 526 | 527 | RRSIG 528 | 4 529 | 530 | 531 | !AAAA 532 | 13 533 | 534 | 535 | NXDOMAIN 536 | 1 537 | 538 | 539 | #A 540 | 18446744073709551603 541 | 542 | 543 | 544 | 1021 545 | 78 546 | 1021 547 | 67 548 | 549 | 550 | 2315 551 | 37 552 | 22 553 | 157 554 | 0 555 | 0 556 | 60 557 | 64 558 | 287392 559 | 49144 560 | 49552 561 | 393216 562 | 132096 563 | 132096 564 | 565 | 566 | 567 | 568 | 569 | 0 570 | 0 571 | 0 572 | 0 573 | 0 574 | 0 575 | 0 576 | 0 577 | 0 578 | 0 579 | 0 580 | 0 581 | 0 582 | 0 583 | 0 584 | 0 585 | 0 586 | 0 587 | 0 588 | 0 589 | 0 590 | 0 591 | 0 592 | 0 593 | 0 594 | 0 595 | 0 596 | 0 597 | 0 598 | 0 599 | 0 600 | 0 601 | 0 602 | 31 603 | 17 604 | 0 605 | 0 606 | 0 607 | 0 608 | 0 609 | 0 610 | 0 611 | 0 612 | 0 613 | 614 | 615 | 616 | 1021 617 | 0 618 | 1021 619 | 0 620 | 621 | 622 | 0 623 | 0 624 | 0 625 | 0 626 | 0 627 | 0 628 | 0 629 | 64 630 | 287392 631 | 29280 632 | 29280 633 | 262144 634 | 1024 635 | 1024 636 | 637 | 638 | 639 | 640 | 641 | 642 | 0x7f1ceb3df650 643 | 2 644 | udp 645 | ::#53 646 | 647 | bound 648 | 649 | 650 | 651 | 0x7f1ceb3df8b0 652 | 4 653 | tcp 654 | ::#53 655 | 656 | listener 657 | bound 658 | 659 | 660 | 661 | 0x7f1ceb3e2b10 662 | 2 663 | udp 664 | 0.0.0.0#53 665 | 666 | bound 667 | 668 | 669 | 670 | 0x7f1ceb3e48b0 671 | 2 672 | tcp 673 | 0.0.0.0#53 674 | 675 | listener 676 | bound 677 | 678 | 679 | 680 | 681 | 682 | 683 | threaded 684 | 5 685 | 5 686 | 1 687 | 0 688 | 689 | 690 | 691 | server 692 | 11 693 | 0x7f1cebc34070 694 | idle 695 | 5 696 | 0 697 | 698 | 699 | zmgr 700 | 5 701 | 0x7f1cebc34138 702 | idle 703 | 1 704 | 0 705 | 706 | 707 | zone 708 | 5 709 | 0x7f1cebc34200 710 | idle 711 | 2 712 | 0 713 | 714 | 715 | loadzone 716 | 2 717 | 0x7f1cebc34b60 718 | idle 719 | 2 720 | 0 721 | 722 | 723 | statchannel 724 | 3 725 | 0x7f1cebc59908 726 | running 727 | 5 728 | 0 729 | 730 | 731 | udpdispatch 732 | 1 733 | 0x7f1cebc599d0 734 | idle 735 | 5 736 | 0 737 | 738 | 739 | udpdispatch 740 | 1 741 | 0x7f1ceb3f6648 742 | idle 743 | 5 744 | 0 745 | 746 | 747 | 748 | 749 | 750 | 751 | 0x7f1cec3b60a0 752 | main 753 | 1273 754 | 31614069 755 | 3630424 756 | 3705111 757 | 1572864 758 | 200 759 | 0 760 | 0 761 | 762 | 763 | 0x7f1cec3b6250 764 | dst 765 | 1 766 | 135557497 767 | 97074 768 | 111296 769 | - 770 | 0 771 | 0 772 | 0 773 | 774 | 775 | 0x7f1cec3b63c0 776 | zonemgr-pool 777 | 43 778 | 10660004492 779 | 6626226696 780 | 7373790225 781 | 7374372864 782 | 0 783 | 0 784 | 0 785 | 786 | 787 | 0x7f1cec3b6560 788 | zonemgr-pool 789 | 28 790 | 285193 791 | 12720 792 | 23769 793 | 262144 794 | 0 795 | 0 796 | 0 797 | 798 | 799 | 0x7f1cea27c1a0 800 | cache 801 | 8 802 | 15929752 803 | 21152 804 | 29424 805 | 262144 806 | 0 807 | 1835008 808 | 1572864 809 | 810 | 811 | 0x7f1cea27c330 812 | cache_heap 813 | 18 814 | 262144 815 | 1024 816 | 1024 817 | 262144 818 | 0 819 | 0 820 | 0 821 | 822 | 823 | 824 | 11494216710 825 | 6631824786 826 | 7398227968 827 | 6933680 828 | 0 829 | 830 | 831 | 832 | --------------------------------------------------------------------------------