├── VERSION ├── MAINTAINERS.md ├── NOTICE ├── .gitignore ├── .dockerignore ├── .github ├── dependabot.yml ├── workflows │ ├── golangci-lint.yml │ └── container_description.yml └── stale.yml ├── CODE_OF_CONDUCT.md ├── SECURITY.md ├── Dockerfile ├── .yamllint ├── .promu.yml ├── .golangci.yml ├── CONTRIBUTING.md ├── Makefile ├── .circleci └── config.yml ├── go.mod ├── CHANGELOG.md ├── README.md ├── main_test.go ├── go.sum ├── Makefile.common ├── LICENSE └── main.go /VERSION: -------------------------------------------------------------------------------- 1 | 0.7.0 2 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | * Julius Volz 2 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Prometheus exporter for collectd. 2 | Copyright 2015 The Prometheus Authors 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.build 2 | /.release 3 | /.tarballs 4 | /collectd_exporter 5 | *.tar.gz 6 | *.test 7 | *-stamp 8 | /vendor 9 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .tarballs/ 3 | 4 | !.build/linux-amd64/ 5 | !.build/linux-armv7 6 | !.build/linux-arm64 7 | !.build/linux-s390x 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ARCH="amd64" 2 | ARG OS="linux" 3 | FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest 4 | LABEL maintainer="The Prometheus Authors " 5 | 6 | ARG ARCH="amd64" 7 | ARG OS="linux" 8 | COPY .build/${OS}-${ARCH}/collectd_exporter /bin/collectd_exporter 9 | 10 | EXPOSE 9103 11 | ENTRYPOINT [ "/bin/collectd_exporter" ] 12 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.promu.yml: -------------------------------------------------------------------------------- 1 | go: 2 | # This must match .circle/config.yml. 3 | version: 1.24 4 | repository: 5 | path: github.com/prometheus/collectd_exporter 6 | build: 7 | ldflags: | 8 | -X github.com/prometheus/common/version.Version={{.Version}} 9 | -X github.com/prometheus/common/version.Revision={{.Revision}} 10 | -X github.com/prometheus/common/version.Branch={{.Branch}} 11 | -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} 12 | -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} 13 | tarball: 14 | files: 15 | - LICENSE 16 | - NOTICE 17 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | - revive 5 | - sloglint 6 | settings: 7 | revive: 8 | rules: 9 | - name: unused-parameter 10 | severity: warning 11 | disabled: true 12 | exclusions: 13 | generated: lax 14 | presets: 15 | - comments 16 | - common-false-positives 17 | - legacy 18 | - std-error-handling 19 | rules: 20 | - linters: 21 | - errcheck 22 | path: _test.go 23 | paths: 24 | - third_party$ 25 | - builtin$ 26 | - examples$ 27 | formatters: 28 | exclusions: 29 | generated: lax 30 | paths: 31 | - third_party$ 32 | - builtin$ 33 | - examples$ 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Prometheus uses GitHub to manage reviews of pull requests. 4 | 5 | * If you have a trivial fix or improvement, go ahead and create a pull request, 6 | addressing (with `@...`) the maintainer of this repository (see 7 | [MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request. 8 | 9 | * If you plan to do something more involved, first discuss your ideas 10 | on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers). 11 | This will avoid unnecessary work and surely give you and us a good deal 12 | of inspiration. 13 | 14 | * Relevant coding style guidelines are the [Go Code Review 15 | Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) 16 | and the _Formatting and style_ section of Peter Bourgon's [Go: Best 17 | Practices for Production 18 | Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2016 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 | # Ensure that 'all' is the default target otherwise it will be the first target from Makefile.common. 15 | all:: 16 | 17 | # Needs to be defined before including Makefile.common to auto-generate targets 18 | DOCKER_ARCHS ?= amd64 armv7 arm64 s390x 19 | 20 | include Makefile.common 21 | 22 | DOCKER_IMAGE_NAME ?= collectd-exporter 23 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2.1 3 | 4 | orbs: 5 | prometheus: prometheus/prometheus@0.17.1 6 | 7 | executors: 8 | # This must match .promu.yml. 9 | golang: 10 | docker: 11 | - image: cimg/go:1.24 12 | 13 | jobs: 14 | test: 15 | executor: golang 16 | 17 | steps: 18 | - prometheus/setup_environment 19 | - run: make 20 | - prometheus/store_artifact: 21 | file: collectd_exporter 22 | - run: git diff --exit-code 23 | 24 | workflows: 25 | version: 2 26 | collectd_exporter: 27 | jobs: 28 | - test: 29 | filters: 30 | tags: 31 | only: /.*/ 32 | - prometheus/build: 33 | name: build 34 | filters: 35 | tags: 36 | only: /.*/ 37 | - prometheus/publish_master: 38 | context: org-context 39 | requires: 40 | - test 41 | - build 42 | filters: 43 | branches: 44 | only: master 45 | - prometheus/publish_release: 46 | context: org-context 47 | requires: 48 | - test 49 | - build 50 | filters: 51 | tags: 52 | only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ 53 | branches: 54 | ignore: /.*/ 55 | -------------------------------------------------------------------------------- /.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 | - name: Install Go 29 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 30 | with: 31 | go-version: 1.24.x 32 | - name: Install snmp_exporter/generator dependencies 33 | run: sudo apt-get update && sudo apt-get -y install libsnmp-dev 34 | if: github.repository == 'prometheus/snmp_exporter' 35 | - name: Lint 36 | uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 37 | with: 38 | args: --verbose 39 | version: v2.1.5 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/prometheus/collectd_exporter 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | collectd.org v0.6.0 7 | github.com/alecthomas/kingpin/v2 v2.4.0 8 | github.com/prometheus/client_golang v1.20.5 9 | github.com/prometheus/common v0.60.1 10 | github.com/prometheus/exporter-toolkit v0.13.1 11 | ) 12 | 13 | require ( 14 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect 15 | github.com/beorn7/perks v1.0.1 // indirect 16 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 17 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 18 | github.com/jpillora/backoff v1.0.0 // indirect 19 | github.com/klauspost/compress v1.17.9 // indirect 20 | github.com/mdlayher/socket v0.4.1 // indirect 21 | github.com/mdlayher/vsock v1.2.1 // indirect 22 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 23 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect 24 | github.com/prometheus/client_model v0.6.1 // indirect 25 | github.com/prometheus/procfs v0.15.1 // indirect 26 | github.com/xhit/go-str2duration/v2 v2.1.0 // indirect 27 | go.uber.org/multierr v1.11.0 // indirect 28 | golang.org/x/crypto v0.28.0 // indirect 29 | golang.org/x/net v0.29.0 // indirect 30 | golang.org/x/oauth2 v0.23.0 // indirect 31 | golang.org/x/sync v0.8.0 // indirect 32 | golang.org/x/sys v0.26.0 // indirect 33 | golang.org/x/text v0.19.0 // indirect 34 | google.golang.org/protobuf v1.34.2 // indirect 35 | gopkg.in/yaml.v2 v2.4.0 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 60 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: false 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - keepalive 16 | 17 | # Set to true to ignore issues in a project (defaults to false) 18 | exemptProjects: false 19 | 20 | # Set to true to ignore issues in a milestone (defaults to false) 21 | exemptMilestones: false 22 | 23 | # Set to true to ignore issues with an assignee (defaults to false) 24 | exemptAssignees: false 25 | 26 | # Label to use when marking as stale 27 | staleLabel: stale 28 | 29 | # Comment to post when marking as stale. Set to `false` to disable 30 | markComment: false 31 | 32 | # Comment to post when removing the stale label. 33 | # unmarkComment: > 34 | # Your comment here. 35 | 36 | # Comment to post when closing a stale Issue or Pull Request. 37 | # closeComment: > 38 | # Your comment here. 39 | 40 | # Limit the number of actions per hour, from 1-30. Default is 30 41 | limitPerRun: 30 42 | 43 | # Limit to only `issues` or `pulls` 44 | only: pulls 45 | 46 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 47 | # pulls: 48 | # daysUntilStale: 30 49 | # markComment: > 50 | # This pull request has been automatically marked as stale because it has not had 51 | # recent activity. It will be closed if no further activity occurs. Thank you 52 | # for your contributions. 53 | 54 | # issues: 55 | # exemptLabels: 56 | # - confirmed 57 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## master / unreleased 2 | 3 | ## 0.7.0 / 2024-11-07 4 | 5 | * [CHANGE] Update logging library #174 6 | 7 | ## 0.6.0 / 2023-12-09 8 | 9 | * [FEATURE] Add TLS authentication using official exporter-toolkit (#113) 10 | * [FEATURE] Added s390x support to docker image (#98) 11 | * [BUGFIX] Restore version command line flag and fix build info (#104) 12 | 13 | ## 0.5.0 / 2020-05-08 14 | 15 | * [CHANGE] Switch logging to go-kit (#85) 16 | 17 | ## 0.4.0 / 2018-01-22 18 | 19 | * [CHANGE] Append `_total` to metric name of collectd Counter and Derive types (#41) 20 | * [CHANGE] Update flag invocation (#47) 21 | * [FEATURE] Support collectd types.db file (#35) 22 | * [FEATURE] Set receive buffer on UDP socket receiving collectd binary data (#36) 23 | * [BUGFIX] Sanitize metric name output (#51) 24 | 25 | ## 0.3.1 / 2016-06-06 26 | 27 | Re-release of 0.3.0 without functional changes due to release process issues. 28 | 29 | ## 0.3.0 / 2016-05-24 30 | 31 | BREAKING CHANGES: 32 | 33 | * To disambiguate metric names, the plugin name will no longer be omitted in most cases (#23) 34 | * The structure of the tarball has changed, see prometheus/docs#447 35 | 36 | All changes: 37 | 38 | * [CHANGE] Logs now have the common Prometheus format 39 | * [CHANGE] Only omit the plugin name if it is equal to the collectd type 40 | * [ENHANCEMENT] The `write_http` example is updated to match the current collectd write_http plugin config format. 41 | * [ENHANCEMENT] Now built with Go 1.6.2. 42 | * [ENHANCEMENT] New release and binary build process. 43 | * [CHANGE] Reorganised release binary tarballs. 44 | 45 | ## 0.2.0 / 2015-09-03 46 | 47 | BREAKING CHANGES: 48 | 49 | * Flag names have been changed to emulate the config options of collectd's 50 | network plugin. Run `./collectd_exporter -h` to see up-to-date flag names. 51 | 52 | All changes: 53 | 54 | * [FEATURE] Implement support for collectd's binary protocol. 55 | * [FEATURE] Add server startup logging. 56 | * [CHANGE] Change flag names to reflect collectd network plugin config options. 57 | * [ENHANCEMENT] Documentation updates and cleanups. 58 | * [ENHANCEMENT] Add unit tests for generated metric names and labels. 59 | * [ENHANCEMENT] New Dockerfile using `alpine-golang-make-onbuild` base image. 60 | * [CLEANUP] Rewrite based on the official collectd Go package. 61 | * [CLEANUP] Update `Makefile.COMMON` from https://github.com/prometheus/utils. 62 | 63 | ## 0.1.0 / 2015-03-28 64 | 65 | Initial release. 66 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Collectd Exporter [![Build Status](https://travis-ci.org/prometheus/collectd_exporter.svg)][travis] 2 | 3 | [![CircleCI](https://circleci.com/gh/prometheus/collectd_exporter/tree/master.svg?style=shield)][circleci] 4 | [![Docker Repository on Quay](https://quay.io/repository/prometheus/collectd-exporter/status)][quay] 5 | [![Docker Pulls](https://img.shields.io/docker/pulls/prom/collectd-exporter.svg?maxAge=604800)][hub] 6 | 7 | An exporter for [collectd](https://collectd.org/). It accepts collectd's 8 | [binary network protocol](https://collectd.org/wiki/index.php/Binary_protocol) 9 | as sent by collectd's 10 | [network plugin](https://collectd.org/wiki/index.php/Plugin:Network) and 11 | metrics in JSON format via HTTP POST as sent by collectd's 12 | [write_http plugin](https://collectd.org/wiki/index.php/Plugin:Write_HTTP), 13 | and transforms and exposes them for consumption by Prometheus. 14 | 15 | This exporter is useful for exporting metrics from existing collectd setups, as 16 | well as for metrics which are not covered by the core Prometheus exporters such 17 | as the [Node Exporter](https://github.com/prometheus/node_exporter). 18 | 19 | ## Binary network protocol 20 | 21 | collectd's *network plugin* uses a lightweight binary protocol to send metrics 22 | from one instance to another. To consume these packets with 23 | *collectd_exporter*, first configure collectd to send these metrics to the 24 | appropriate address: 25 | 26 | ``` 27 | LoadPlugin network 28 | 29 | Server "prometheus.example.com" "25826" 30 | 31 | ``` 32 | 33 | Then start *collectd_exporter* with `--collectd.listen-address=":25826"` to 34 | start consuming and exporting these metrics. 35 | 36 | ## JSON format 37 | 38 | collectd's *write_http plugin* is able to send metrics via HTTP POST requests. 39 | *collectd_exporter* serves an appropriate end-point which accepts, parses and 40 | exports the metrics. First, configure collectd to send these metrics to the 41 | HTTP end-point: 42 | 43 | ``` 44 | LoadPlugin write_http 45 | 46 | 47 | URL "http://localhost:9103/collectd-post" 48 | Format "JSON" 49 | StoreRates false 50 | 51 | 52 | ``` 53 | 54 | To change the path of the end-point, use the `--web.collectd-push-path` command 55 | line option. To disable this functionality altogether, use 56 | `--web.collectd-push-path=""`. 57 | 58 | ## Using Docker 59 | 60 | You can deploy this exporter using the [prom/collectd-exporter][hub] Docker image. 61 | You will need to map the collectd port from the container to the host, remembering 62 | that this is a UDP port. 63 | 64 | For example: 65 | 66 | ```bash 67 | docker pull prom/collectd-exporter 68 | 69 | docker run -d -p 9103:9103 -p 25826:25826/udp prom/collectd-exporter --collectd.listen-address=":25826" 70 | ``` 71 | 72 | ## TLS and basic authentication 73 | 74 | The *collectd_exporter* supports TLS and basic authentication. 75 | To use TLS and/or basic authentication, you need to pass a configuration file 76 | using the `--web.config.file` parameter. The format of the file is described 77 | [in the exporter-toolkit repository](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md). 78 | 79 | [circleci]: https://circleci.com/gh/prometheus/collectd_exporter 80 | [hub]: https://hub.docker.com/r/prom/collectd-exporter/ 81 | [travis]: https://travis-ci.org/prometheus/collectd_exporter 82 | [quay]: https://quay.io/repository/prometheus/collectd-exporter 83 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 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 | package main 15 | 16 | import ( 17 | "reflect" 18 | "testing" 19 | 20 | "collectd.org/api" 21 | "github.com/prometheus/client_golang/prometheus" 22 | ) 23 | 24 | func TestNewName(t *testing.T) { 25 | cases := []struct { 26 | vl api.ValueList 27 | index int 28 | want string 29 | }{ 30 | {api.ValueList{ 31 | Identifier: api.Identifier{ 32 | Plugin: "cpu", 33 | Type: "cpu", 34 | }, 35 | DSNames: []string{"value"}, 36 | Values: []api.Value{api.Derive(0)}, 37 | }, 0, "collectd_cpu_total"}, 38 | {api.ValueList{ 39 | Identifier: api.Identifier{ 40 | Plugin: "dns", 41 | Type: "dns_qtype", 42 | }, 43 | DSNames: []string{"value"}, 44 | Values: []api.Value{api.Derive(0)}, 45 | }, 0, "collectd_dns_dns_qtype_total"}, 46 | {api.ValueList{ 47 | Identifier: api.Identifier{ 48 | Plugin: "df", 49 | Type: "df", 50 | }, 51 | DSNames: []string{"used", "free"}, 52 | Values: []api.Value{api.Gauge(0), api.Gauge(1)}, 53 | }, 0, "collectd_df_used"}, 54 | {api.ValueList{ 55 | Identifier: api.Identifier{ 56 | Plugin: "df", 57 | Type: "df", 58 | }, 59 | DSNames: []string{"used", "free"}, 60 | Values: []api.Value{api.Gauge(0), api.Gauge(1)}, 61 | }, 1, "collectd_df_free"}, 62 | {api.ValueList{ 63 | Identifier: api.Identifier{ 64 | Plugin: "cpu", 65 | Type: "percent", 66 | }, 67 | DSNames: []string{"value"}, 68 | Values: []api.Value{api.Gauge(0)}, 69 | }, 0, "collectd_cpu_percent"}, 70 | {api.ValueList{ 71 | Identifier: api.Identifier{ 72 | Plugin: "interface", 73 | Type: "if_octets", 74 | }, 75 | DSNames: []string{"rx", "tx"}, 76 | Values: []api.Value{api.Counter(0), api.Counter(1)}, 77 | }, 0, "collectd_interface_if_octets_rx_total"}, 78 | {api.ValueList{ 79 | Identifier: api.Identifier{ 80 | Plugin: "interface", 81 | Type: "if_octets", 82 | }, 83 | DSNames: []string{"rx", "tx"}, 84 | Values: []api.Value{api.Counter(0), api.Counter(1)}, 85 | }, 1, "collectd_interface_if_octets_tx_total"}, 86 | {api.ValueList{ 87 | Identifier: api.Identifier{ 88 | Plugin: "docker", 89 | Type: "cpu.percent", 90 | }, 91 | DSNames: []string{"value"}, 92 | Values: []api.Value{api.Gauge(0)}, 93 | }, 0, "collectd_docker_cpu_percent"}, 94 | } 95 | 96 | for _, c := range cases { 97 | got := newName(c.vl, c.index) 98 | if got != c.want { 99 | t.Errorf("newName(%v): got %q, want %q", c.vl, got, c.want) 100 | } 101 | } 102 | } 103 | 104 | func TestNewLabels(t *testing.T) { 105 | cases := []struct { 106 | vl api.ValueList 107 | want prometheus.Labels 108 | }{ 109 | {api.ValueList{ 110 | Identifier: api.Identifier{ 111 | Host: "example.com", 112 | Plugin: "cpu", 113 | PluginInstance: "0", 114 | Type: "cpu", 115 | TypeInstance: "user", 116 | }, 117 | }, prometheus.Labels{ 118 | "cpu": "0", 119 | "type": "user", 120 | "instance": "example.com", 121 | }}, 122 | {api.ValueList{ 123 | Identifier: api.Identifier{ 124 | Host: "example.com", 125 | Plugin: "df", 126 | Type: "df_complex", 127 | TypeInstance: "used", 128 | }, 129 | }, prometheus.Labels{ 130 | "df": "used", 131 | "instance": "example.com", 132 | }}, 133 | {api.ValueList{ 134 | Identifier: api.Identifier{ 135 | Host: "example.com", 136 | Plugin: "load", 137 | Type: "load", 138 | }, 139 | }, prometheus.Labels{ 140 | "instance": "example.com", 141 | }}, 142 | } 143 | 144 | for _, c := range cases { 145 | got := newLabels(c.vl) 146 | if !reflect.DeepEqual(got, c.want) { 147 | t.Errorf("newLabels(%v): got %v, want %v", c.vl, got, c.want) 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | collectd.org v0.6.0 h1:wDTcB13Zork7m9bEHmU2sVL4z+hxBmm8EyeMjjxtW7s= 2 | collectd.org v0.6.0/go.mod h1:fXcRZb1qBKshIHJa2T8qBS7Xew/I43iMutefnTdGeYo= 3 | github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= 4 | github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= 5 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= 6 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 7 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 8 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 9 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 10 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 11 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 12 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 17 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 18 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 19 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 20 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 21 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 22 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 23 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 24 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 25 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 26 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 27 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 28 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 29 | github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= 30 | github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 31 | github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= 32 | github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= 33 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 34 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 35 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= 36 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 37 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 38 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 39 | github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= 40 | github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 41 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 42 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 43 | github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= 44 | github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= 45 | github.com/prometheus/exporter-toolkit v0.13.1 h1:Evsh0gWQo2bdOHlnz9+0Nm7/OFfIwhE2Ws4A2jIlR04= 46 | github.com/prometheus/exporter-toolkit v0.13.1/go.mod h1:ujdv2YIOxtdFxxqtloLpbqmxd5J0Le6IITUvIRSWjj0= 47 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 48 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 49 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 50 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 51 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 52 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 53 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 54 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 55 | github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= 56 | github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= 57 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 58 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 59 | golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= 60 | golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 61 | golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= 62 | golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 63 | golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= 64 | golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 65 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 66 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 67 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 68 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 69 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 70 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 71 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 72 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 73 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 74 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 75 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 76 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 77 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 78 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 79 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 80 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 81 | -------------------------------------------------------------------------------- /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.1.5 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 $$(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 $$(find . -type f -iname '*.go' ! -path './vendor/*') ; 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 -d $$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: precheck 270 | precheck:: 271 | 272 | define PRECHECK_COMMAND_template = 273 | precheck:: $(1)_precheck 274 | 275 | PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) 276 | .PHONY: $(1)_precheck 277 | $(1)_precheck: 278 | @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ 279 | echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ 280 | exit 1; \ 281 | fi 282 | endef 283 | 284 | govulncheck: install-govulncheck 285 | govulncheck ./... 286 | 287 | install-govulncheck: 288 | command -v govulncheck > /dev/null || go install golang.org/x/vuln/cmd/govulncheck@latest 289 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 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 | package main 15 | 16 | import ( 17 | "context" 18 | "encoding/json" 19 | "fmt" 20 | "io" 21 | "log/slog" 22 | "net" 23 | "net/http" 24 | "os" 25 | "regexp" 26 | "strings" 27 | "sync" 28 | "time" 29 | 30 | "collectd.org/api" 31 | "collectd.org/network" 32 | "github.com/alecthomas/kingpin/v2" 33 | "github.com/prometheus/client_golang/prometheus" 34 | versioncollector "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 | "github.com/prometheus/exporter-toolkit/web/kingpinflag" 41 | ) 42 | 43 | // timeout specifies the number of iterations after which a metric times out, 44 | // i.e. becomes stale and is removed from collectdCollector.valueLists. It is 45 | // modeled and named after the top-level "Timeout" setting of collectd. 46 | const timeout = 2 47 | 48 | var ( 49 | collectdAddress = kingpin.Flag("collectd.listen-address", "Network address on which to accept collectd binary network packets, e.g. \":25826\".").Default("").String() 50 | collectdBuffer = kingpin.Flag("collectd.udp-buffer", "Size of the receive buffer of the socket used by collectd binary protocol receiver.").Default("0").Int() 51 | collectdAuth = kingpin.Flag("collectd.auth-file", "File mapping user names to pre-shared keys (passwords).").Default("").String() 52 | collectdSecurity = kingpin.Flag("collectd.security-level", "Minimum required security level for accepted packets. Must be one of \"None\", \"Sign\" and \"Encrypt\".").Default("None").String() 53 | collectdTypesDB = kingpin.Flag("collectd.typesdb-file", "Collectd types.db file for datasource names mapping. Needed only if using a binary network protocol.").Default("").String() 54 | metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose Prometheus metrics.").Default("/metrics").String() 55 | collectdPostPath = kingpin.Flag("web.collectd-push-path", "Path under which to accept POST requests from collectd.").Default("/collectd-post").String() 56 | lastPush = prometheus.NewGauge( 57 | prometheus.GaugeOpts{ 58 | Name: "collectd_last_push_timestamp_seconds", 59 | Help: "Unix timestamp of the last received collectd metrics push in seconds.", 60 | }, 61 | ) 62 | metric_name_re = regexp.MustCompile("[^a-zA-Z0-9_:]") 63 | ) 64 | 65 | // newName converts one data source of a value list to a string representation. 66 | func newName(vl api.ValueList, index int) string { 67 | var name string 68 | if vl.Plugin == vl.Type { 69 | name = "collectd_" + vl.Type 70 | } else { 71 | name = "collectd_" + vl.Plugin + "_" + vl.Type 72 | } 73 | if dsname := vl.DSName(index); dsname != "value" { 74 | name += "_" + dsname 75 | } 76 | switch vl.Values[index].(type) { 77 | case api.Counter, api.Derive: 78 | name += "_total" 79 | } 80 | 81 | return metric_name_re.ReplaceAllString(name, "_") 82 | } 83 | 84 | // newLabels converts the plugin and type instance of vl to a set of prometheus.Labels. 85 | func newLabels(vl api.ValueList) prometheus.Labels { 86 | labels := prometheus.Labels{} 87 | if vl.PluginInstance != "" { 88 | labels[vl.Plugin] = vl.PluginInstance 89 | } 90 | if vl.TypeInstance != "" { 91 | if vl.PluginInstance == "" { 92 | labels[vl.Plugin] = vl.TypeInstance 93 | } else { 94 | labels["type"] = vl.TypeInstance 95 | } 96 | } 97 | labels["instance"] = vl.Host 98 | 99 | return labels 100 | } 101 | 102 | // newDesc converts one data source of a value list to a Prometheus description. 103 | func newDesc(vl api.ValueList, index int) *prometheus.Desc { 104 | help := fmt.Sprintf("Collectd exporter: '%s' Type: '%s' Dstype: '%T' Dsname: '%s'", 105 | vl.Plugin, vl.Type, vl.Values[index], vl.DSName(index)) 106 | 107 | return prometheus.NewDesc(newName(vl, index), help, []string{}, newLabels(vl)) 108 | } 109 | 110 | // newMetric converts one data source of a value list to a Prometheus metric. 111 | func newMetric(vl api.ValueList, index int) (prometheus.Metric, error) { 112 | var value float64 113 | var valueType prometheus.ValueType 114 | 115 | switch v := vl.Values[index].(type) { 116 | case api.Gauge: 117 | value = float64(v) 118 | valueType = prometheus.GaugeValue 119 | case api.Derive: 120 | value = float64(v) 121 | valueType = prometheus.CounterValue 122 | case api.Counter: 123 | value = float64(v) 124 | valueType = prometheus.CounterValue 125 | default: 126 | return nil, fmt.Errorf("unknown value type: %T", v) 127 | } 128 | 129 | return prometheus.NewConstMetric(newDesc(vl, index), valueType, value) 130 | } 131 | 132 | type collectdCollector struct { 133 | ch chan api.ValueList 134 | valueLists map[string]api.ValueList 135 | mu *sync.Mutex 136 | logger *slog.Logger 137 | } 138 | 139 | func newCollectdCollector(logger *slog.Logger) *collectdCollector { 140 | c := &collectdCollector{ 141 | ch: make(chan api.ValueList), 142 | valueLists: make(map[string]api.ValueList), 143 | mu: &sync.Mutex{}, 144 | logger: logger, 145 | } 146 | go c.processSamples() 147 | return c 148 | } 149 | 150 | func (c *collectdCollector) collectdPost(w http.ResponseWriter, r *http.Request) { 151 | data, err := io.ReadAll(r.Body) 152 | if err != nil { 153 | http.Error(w, err.Error(), http.StatusInternalServerError) 154 | return 155 | } 156 | 157 | var valueLists []*api.ValueList 158 | if err := json.Unmarshal(data, &valueLists); err != nil { 159 | http.Error(w, err.Error(), http.StatusBadRequest) 160 | return 161 | } 162 | 163 | for _, vl := range valueLists { 164 | err := c.Write(r.Context(), vl) 165 | if err != nil { 166 | c.logger.Debug("error writing collectd post", "error", err) 167 | } 168 | } 169 | } 170 | 171 | func (c *collectdCollector) processSamples() { 172 | ticker := time.NewTicker(time.Minute).C 173 | for { 174 | select { 175 | case vl := <-c.ch: 176 | id := vl.String() 177 | c.mu.Lock() 178 | c.valueLists[id] = vl 179 | c.mu.Unlock() 180 | 181 | case <-ticker: 182 | // Garbage collect expired value lists. 183 | now := time.Now() 184 | c.mu.Lock() 185 | for id, vl := range c.valueLists { 186 | validUntil := vl.Time.Add(timeout * vl.Interval) 187 | if validUntil.Before(now) { 188 | delete(c.valueLists, id) 189 | } 190 | } 191 | c.mu.Unlock() 192 | } 193 | } 194 | } 195 | 196 | // Collect implements prometheus.Collector. 197 | func (c collectdCollector) Collect(ch chan<- prometheus.Metric) { 198 | ch <- lastPush 199 | 200 | c.mu.Lock() 201 | valueLists := make([]api.ValueList, 0, len(c.valueLists)) 202 | for _, vl := range c.valueLists { 203 | valueLists = append(valueLists, vl) 204 | } 205 | c.mu.Unlock() 206 | 207 | now := time.Now() 208 | for _, vl := range valueLists { 209 | validUntil := vl.Time.Add(timeout * vl.Interval) 210 | if validUntil.Before(now) { 211 | continue 212 | } 213 | 214 | for i := range vl.Values { 215 | m, err := newMetric(vl, i) 216 | if err != nil { 217 | c.logger.Error("Error converting collectd data type to a Prometheus metric", "err", err) 218 | continue 219 | } 220 | 221 | ch <- m 222 | } 223 | } 224 | } 225 | 226 | // Describe implements prometheus.Collector. 227 | func (c collectdCollector) Describe(ch chan<- *prometheus.Desc) { 228 | ch <- lastPush.Desc() 229 | } 230 | 231 | // Write writes "vl" to the collector's channel, to be (asynchronously) 232 | // processed by processSamples(). It implements api.Writer. 233 | func (c collectdCollector) Write(_ context.Context, vl *api.ValueList) error { 234 | lastPush.Set(float64(time.Now().UnixNano()) / 1e9) 235 | c.ch <- *vl 236 | 237 | return nil 238 | } 239 | 240 | func startCollectdServer(ctx context.Context, w api.Writer, logger *slog.Logger) { 241 | if *collectdAddress == "" { 242 | return 243 | } 244 | 245 | srv := network.Server{ 246 | Addr: *collectdAddress, 247 | Writer: w, 248 | } 249 | 250 | if *collectdAuth != "" { 251 | srv.PasswordLookup = network.NewAuthFile(*collectdAuth) 252 | } 253 | 254 | if *collectdTypesDB != "" { 255 | file, err := os.Open(*collectdTypesDB) 256 | if err != nil { 257 | logger.Error("Can't open types.db file", "types", *collectdTypesDB, "err", err) 258 | os.Exit(1) 259 | } 260 | defer file.Close() 261 | 262 | typesDB, err := api.NewTypesDB(file) 263 | if err != nil { 264 | logger.Error("Error in parsing types.db file", "types", *collectdTypesDB, "err", err) 265 | os.Exit(1) 266 | } 267 | srv.TypesDB = typesDB 268 | } 269 | 270 | switch strings.ToLower(*collectdSecurity) { 271 | case "", "none": 272 | srv.SecurityLevel = network.None 273 | case "sign": 274 | srv.SecurityLevel = network.Sign 275 | case "encrypt": 276 | srv.SecurityLevel = network.Encrypt 277 | default: 278 | logger.Error("Unknown security level provided. Must be one of \"None\", \"Sign\" and \"Encrypt\"", "level", *collectdSecurity) 279 | os.Exit(1) 280 | } 281 | 282 | laddr, err := net.ResolveUDPAddr("udp", *collectdAddress) 283 | if err != nil { 284 | logger.Error("Failed to resolve binary protocol listening UDP address", "address", *collectdAddress, "err", err) 285 | os.Exit(1) 286 | } 287 | 288 | if laddr.IP != nil && laddr.IP.IsMulticast() { 289 | srv.Conn, err = net.ListenMulticastUDP("udp", nil, laddr) 290 | } else { 291 | srv.Conn, err = net.ListenUDP("udp", laddr) 292 | } 293 | if err != nil { 294 | logger.Error("Failed to create a socket for a binary protocol server", "err", err) 295 | os.Exit(1) 296 | } 297 | if *collectdBuffer > 0 { 298 | if err = srv.Conn.SetReadBuffer(*collectdBuffer); err != nil { 299 | logger.Error("Failed to adjust a read buffer of the socket", "err", err) 300 | os.Exit(1) 301 | } 302 | } 303 | 304 | go func() { 305 | if err := srv.ListenAndWrite(ctx); err != nil { 306 | logger.Error("Error starting collectd server", "err", err) 307 | os.Exit(1) 308 | } 309 | }() 310 | } 311 | 312 | func init() { 313 | prometheus.MustRegister(versioncollector.NewCollector("collectd_exporter")) 314 | } 315 | 316 | func main() { 317 | promslogConfig := &promslog.Config{} 318 | toolkitFlags := kingpinflag.AddFlags(kingpin.CommandLine, ":9103") 319 | flag.AddFlags(kingpin.CommandLine, promslogConfig) 320 | kingpin.Version(version.Print("collectd_exporter")) 321 | kingpin.HelpFlag.Short('h') 322 | kingpin.Parse() 323 | logger := promslog.New(promslogConfig) 324 | 325 | logger.Info("Starting collectd_exporter", "version", version.Info()) 326 | logger.Info("Build context", "context", version.BuildContext()) 327 | 328 | c := newCollectdCollector(logger) 329 | prometheus.MustRegister(c) 330 | 331 | startCollectdServer(context.Background(), c, logger) 332 | 333 | if *collectdPostPath != "" { 334 | http.HandleFunc(*collectdPostPath, c.collectdPost) 335 | } 336 | 337 | http.Handle(*metricsPath, promhttp.Handler()) 338 | if *metricsPath != "/" { 339 | 340 | landingConfig := web.LandingConfig{ 341 | Name: "collectd_exporter", 342 | Description: "Prometheus Collectd Exporter", 343 | Version: version.Info(), 344 | Links: []web.LandingLinks{ 345 | { 346 | Address: *metricsPath, 347 | Text: "Metrics", 348 | }, 349 | }, 350 | } 351 | landingPage, err := web.NewLandingPage(landingConfig) 352 | if err != nil { 353 | logger.Error("Error creating landing page", "err", err) 354 | os.Exit(1) 355 | } 356 | http.Handle("/", landingPage) 357 | } 358 | 359 | srv := &http.Server{} 360 | if err := web.ListenAndServe(srv, toolkitFlags, logger); err != nil { 361 | logger.Error("Error starting HTTP server", "err", err) 362 | os.Exit(1) 363 | } 364 | } 365 | --------------------------------------------------------------------------------