pax_global_header00006660000000000000000000000064136036117620014517gustar00rootroot0000000000000052 comment=6524b918016e48eade6132a2bc863a6f87328433 prometheus-snmp-exporter-0.16.1+ds/000077500000000000000000000000001360361176200172225ustar00rootroot00000000000000prometheus-snmp-exporter-0.16.1+ds/.circleci/000077500000000000000000000000001360361176200210555ustar00rootroot00000000000000prometheus-snmp-exporter-0.16.1+ds/.circleci/config.yml000066400000000000000000000066261360361176200230570ustar00rootroot00000000000000--- version: 2.1 orbs: prometheus: prometheus/prometheus@0.1.0 executors: # Whenever the Go version is updated here, .travis.yml and .promu.yml # should also be updated. golang: docker: - image: circleci/golang:1.13 parameters: working_dir: type: string default: ~/project working_directory: << parameters.working_dir >> jobs: test: executor: golang steps: - prometheus/setup_environment - run: sudo apt-get -y install build-essential libsnmp-dev - run: make - run: git diff --exit-code - prometheus/store_artifact: file: snmp_exporter generator: executor: name: golang working_dir: ~/project/generator environment: MIBDIRS: mibs steps: - checkout: path: ~/project - run: sudo apt-get -y install build-essential diffutils libsnmp-dev - run: make mibs - run: make generator - run: make parse_errors - run: make generate - run: diff -u ../snmp.yml snmp.yml publish_generator_master: executor: golang steps: - prometheus/setup_build_environment - run: make -C generator docker - run: make -C generator docker DOCKER_REPO=quay.io/prometheus - run: docker images - run: docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD - run: docker login -u $QUAY_LOGIN -p $QUAY_PASSWORD quay.io - run: make -C generator docker-publish - run: make -C generator docker-publish DOCKER_REPO=quay.io/prometheus publish_generator_release: executor: golang steps: - prometheus/setup_build_environment - run: make -C generator docker DOCKER_IMAGE_TAG=$CIRCLE_TAG - run: make -C generator docker DOCKER_IMAGE_TAG=$CIRCLE_TAG DOCKER_REPO=quay.io/prometheus - run: docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD - run: docker login -u $QUAY_LOGIN -p $QUAY_PASSWORD quay.io - run: | if [[ "$CIRCLE_TAG" =~ ^v[0-9]+(\.[0-9]+){2}$ ]]; then make -C generator docker-tag-latest DOCKER_IMAGE_TAG="$CIRCLE_TAG" make -C generator docker-tag-latest DOCKER_IMAGE_TAG="$CIRCLE_TAG" DOCKER_REPO=quay.io/prometheus fi - run: make -C generator docker-publish - run: make -C generator docker-publish DOCKER_REPO=quay.io/prometheus workflows: version: 2 snmp_exporter: jobs: - test: filters: tags: only: /.*/ - prometheus/build: name: build filters: tags: only: /.*/ - generator: filters: tags: only: /.*/ - prometheus/publish_master: context: org-context requires: - test - build - generator filters: branches: only: master - prometheus/publish_release: context: org-context requires: - test - build - generator filters: tags: only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ branches: ignore: /.*/ - publish_generator_master: context: org-context requires: - test - build - generator filters: branches: only: master - publish_generator_release: context: org-context requires: - test - build - generator filters: tags: only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ branches: ignore: /.*/ prometheus-snmp-exporter-0.16.1+ds/.dockerignore000066400000000000000000000001211360361176200216700ustar00rootroot00000000000000.build/ .tarballs/ !.build/linux-amd64/ !.build/linux-armv7 !.build/linux-arm64 prometheus-snmp-exporter-0.16.1+ds/.github/000077500000000000000000000000001360361176200205625ustar00rootroot00000000000000prometheus-snmp-exporter-0.16.1+ds/.github/ISSUE_TEMPLATE.md000066400000000000000000000017031360361176200232700ustar00rootroot00000000000000 ### Host operating system: output of `uname -a` ### snmp_exporter version: output of `snmp_exporter -version` ### What device/snmpwalk OID are you using? ### If this is a new device, please link to the MIB(s). ### What did you do that produced an error? ### What did you expect to see? ### What did you see instead? prometheus-snmp-exporter-0.16.1+ds/.gitignore000066400000000000000000000005561360361176200212200ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe dependencies-stamp /snmp_exporter /.build /.release /.tarballs .deps *.tar.gz generator/generator generator/mibs prometheus-snmp-exporter-0.16.1+ds/.golangci.yml000066400000000000000000000002541360361176200216070ustar00rootroot00000000000000run: modules-download-mode: vendor # Run only staticcheck for now. Additional linters will be enabled one-by-one. linters: enable: - staticcheck disable-all: true prometheus-snmp-exporter-0.16.1+ds/.promu.yml000066400000000000000000000025131360361176200211660ustar00rootroot00000000000000go: # Whenever the Go version is updated here, .travis.yml and # .circle/config.yml should also be updated. version: 1.13 repository: path: github.com/prometheus/snmp_exporter build: flags: -mod=vendor -a -tags netgo ldflags: | -X github.com/prometheus/common/version.Version={{.Version}} -X github.com/prometheus/common/version.Revision={{.Revision}} -X github.com/prometheus/common/version.Branch={{.Branch}} -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} tarball: files: - snmp.yml - LICENSE - NOTICE crossbuild: platforms: - linux/amd64 - linux/386 - darwin/amd64 - darwin/386 - windows/amd64 - windows/386 - freebsd/amd64 - freebsd/386 - openbsd/amd64 - openbsd/386 - netbsd/amd64 - netbsd/386 - dragonfly/amd64 - linux/arm - linux/arm64 # Temporarily deactivated as golang.org/x/sys does not have syscalls # implemented for that os/platform combination. #- freebsd/arm #- openbsd/arm #- linux/mips64 #- linux/mips64le - netbsd/arm - linux/ppc64 - linux/ppc64le prometheus-snmp-exporter-0.16.1+ds/.travis.yml000066400000000000000000000002701360361176200213320ustar00rootroot00000000000000sudo: false language: go # Whenever the Go version is updated here, .circleci/config.yml and .promu.yml # should also be updated. go: - 1.13.x script: - make - git diff --exit-code prometheus-snmp-exporter-0.16.1+ds/CONTRIBUTING.md000066400000000000000000000015461360361176200214610ustar00rootroot00000000000000# Contributing Prometheus uses GitHub to manage reviews of pull requests. * If you have a trivial fix or improvement, go ahead and create a pull request, addressing (with `@...`) the maintainer of this repository (see [MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request. * If you plan to do something more involved, first discuss your ideas on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers). This will avoid unnecessary work and surely give you and us a good deal of inspiration. * Relevant coding style guidelines are the [Go Code Review Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) and the _Formatting and style_ section of Peter Bourgon's [Go: Best Practices for Production Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). prometheus-snmp-exporter-0.16.1+ds/Dockerfile000066400000000000000000000006501360361176200212150ustar00rootroot00000000000000ARG ARCH="amd64" ARG OS="linux" FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest LABEL maintainer="The Prometheus Authors " ARG ARCH="amd64" ARG OS="linux" COPY .build/${OS}-${ARCH}/snmp_exporter /bin/snmp_exporter COPY snmp.yml /etc/snmp_exporter/snmp.yml EXPOSE 9116 ENTRYPOINT [ "/bin/snmp_exporter" ] CMD [ "--config.file=/etc/snmp_exporter/snmp.yml" ] prometheus-snmp-exporter-0.16.1+ds/LICENSE000066400000000000000000000261351360361176200202360ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. prometheus-snmp-exporter-0.16.1+ds/MAINTAINERS.md000066400000000000000000000000621360361176200213140ustar00rootroot00000000000000* Brian Brazil prometheus-snmp-exporter-0.16.1+ds/Makefile000066400000000000000000000014651360361176200206700ustar00rootroot00000000000000# Copyright 2016 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Needs to be defined before including Makefile.common to auto-generate targets DOCKER_ARCHS ?= amd64 armv7 arm64 include Makefile.common STATICCHECK_IGNORE = DOCKER_IMAGE_NAME ?= snmp-exporter ifdef DEBUG bindata_flags = -debug endif prometheus-snmp-exporter-0.16.1+ds/Makefile.common000066400000000000000000000217771360361176200221670ustar00rootroot00000000000000# Copyright 2018 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # A common Makefile that includes rules to be reused in different prometheus projects. # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! # Example usage : # Create the main Makefile in the root project directory. # include Makefile.common # customTarget: # @echo ">> Running customTarget" # # Ensure GOBIN is not set during build so that promu is installed to the correct path unexport GOBIN GO ?= go GOFMT ?= $(GO)fmt FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) GOOPTS ?= GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) GO_VERSION ?= $(shell $(GO) version) GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') GOVENDOR := GO111MODULE := ifeq (, $(PRE_GO_111)) ifneq (,$(wildcard go.mod)) # Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI). GO111MODULE := on ifneq (,$(wildcard vendor)) # Always use the local vendor/ directory to satisfy the dependencies. GOOPTS := $(GOOPTS) -mod=vendor endif endif else ifneq (,$(wildcard go.mod)) ifneq (,$(wildcard vendor)) $(warning This repository requires Go >= 1.11 because of Go modules) $(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)') endif else # This repository isn't using Go modules (yet). GOVENDOR := $(FIRST_GOPATH)/bin/govendor endif endif PROMU := $(FIRST_GOPATH)/bin/promu pkgs = ./... ifeq (arm, $(GOHOSTARCH)) GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) else GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) endif PROMU_VERSION ?= 0.5.0 PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_VERSION ?= v1.17.1 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint endif endif PREFIX ?= $(shell pwd) BIN_DIR ?= $(shell pwd) DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) DOCKERFILE_PATH ?= ./Dockerfile DOCKERBUILD_CONTEXT ?= ./ DOCKER_REPO ?= prom DOCKER_ARCHS ?= amd64 BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) ifeq ($(GOHOSTARCH),amd64) ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) # Only supported on amd64 test-flags := -race endif endif # This rule is used to forward a target like "build" to "common-build". This # allows a new "build" target to be defined in a Makefile which includes this # one and override "common-build" without override warnings. %: common-% ; .PHONY: common-all common-all: precheck style check_license lint unused build test .PHONY: common-style common-style: @echo ">> checking code style" @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ if [ -n "$${fmtRes}" ]; then \ echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ echo "Please ensure you are using $$($(GO) version) for formatting code."; \ exit 1; \ fi .PHONY: common-check_license common-check_license: @echo ">> checking license header" @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ done); \ if [ -n "$${licRes}" ]; then \ echo "license header checking failed:"; echo "$${licRes}"; \ exit 1; \ fi .PHONY: common-deps common-deps: @echo ">> getting dependencies" ifdef GO111MODULE GO111MODULE=$(GO111MODULE) $(GO) mod download else $(GO) get $(GOOPTS) -t ./... endif .PHONY: common-test-short common-test-short: @echo ">> running short tests" GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs) .PHONY: common-test common-test: @echo ">> running all tests" GO111MODULE=$(GO111MODULE) $(GO) test $(test-flags) $(GOOPTS) $(pkgs) .PHONY: common-format common-format: @echo ">> formatting code" GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs) .PHONY: common-vet common-vet: @echo ">> vetting code" GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs) .PHONY: common-lint common-lint: $(GOLANGCI_LINT) ifdef GOLANGCI_LINT @echo ">> running golangci-lint" ifdef GO111MODULE # 'go list' needs to be executed before staticcheck to prepopulate the modules cache. # Otherwise staticcheck might fail randomly for some reason not yet explained. GO111MODULE=$(GO111MODULE) $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) else $(GOLANGCI_LINT) run $(pkgs) endif endif # For backward-compatibility. .PHONY: common-staticcheck common-staticcheck: lint .PHONY: common-unused common-unused: $(GOVENDOR) ifdef GOVENDOR @echo ">> running check for unused packages" @$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages' else ifdef GO111MODULE @echo ">> running check for unused/missing packages in go.mod" GO111MODULE=$(GO111MODULE) $(GO) mod tidy ifeq (,$(wildcard vendor)) @git diff --exit-code -- go.sum go.mod else @echo ">> running check for unused packages in vendor/" GO111MODULE=$(GO111MODULE) $(GO) mod vendor @git diff --exit-code -- go.sum go.mod vendor/ endif endif endif .PHONY: common-build common-build: promu @echo ">> building binaries" GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) .PHONY: common-tarball common-tarball: promu @echo ">> building release tarball" $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) .PHONY: common-docker $(BUILD_DOCKER_ARCHS) common-docker: $(BUILD_DOCKER_ARCHS) $(BUILD_DOCKER_ARCHS): common-docker-%: docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \ -f $(DOCKERFILE_PATH) \ --build-arg ARCH="$*" \ --build-arg OS="linux" \ $(DOCKERBUILD_CONTEXT) .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) common-docker-publish: $(PUBLISH_DOCKER_ARCHS) $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) common-docker-tag-latest: $(TAG_DOCKER_ARCHS) $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" .PHONY: common-docker-manifest common-docker-manifest: DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG)) DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" .PHONY: promu promu: $(PROMU) $(PROMU): $(eval PROMU_TMP := $(shell mktemp -d)) curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) mkdir -p $(FIRST_GOPATH)/bin cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu rm -r $(PROMU_TMP) .PHONY: proto proto: @echo ">> generating code from proto files" @./scripts/genproto.sh ifdef GOLANGCI_LINT $(GOLANGCI_LINT): mkdir -p $(FIRST_GOPATH)/bin curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ | sed -e '/install -d/d' \ | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) endif ifdef GOVENDOR .PHONY: $(GOVENDOR) $(GOVENDOR): GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor endif .PHONY: precheck precheck:: define PRECHECK_COMMAND_template = precheck:: $(1)_precheck PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) .PHONY: $(1)_precheck $(1)_precheck: @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ exit 1; \ fi endef prometheus-snmp-exporter-0.16.1+ds/NOTICE000066400000000000000000000000771360361176200201320ustar00rootroot00000000000000Prometheus SNMP exporter Copyright 2016 The Prometheus Authors prometheus-snmp-exporter-0.16.1+ds/README.md000066400000000000000000000043711360361176200205060ustar00rootroot00000000000000# Prometheus SNMP Exporter This is an exporter that exposes information gathered from SNMP for use by the Prometheus monitoring system. There are two components. An exporter that does the actual scraping, and a [generator](generator/) (which depends on NetSNMP) that creates the configuration for use by the exporter. ## Installation Binaries can be downloaded from the [Github releases](https://github.com/prometheus/snmp_exporter/releases) page. ## Usage ```sh ./snmp_exporter ``` Visit http://localhost:9116/snmp?target=1.2.3.4 where 1.2.3.4 is the IP of the SNMP device to get metrics from. You can also specify a `module` parameter, to choose which module to use from the config file. ## Configuration The snmp exporter reads from a `snmp.yml` config file by default. This file is not intended to be written by hand, rather use the [generator](generator/) to generate it for you. The default `snmp.yml` covers a variety of common hardware for which MIBs are available to the public, walking them using SNMP v2 GETBULK. You'll need to use the generator in all but the simplest of setups. It is needed to customize which objects are walked, use non-public MIBs or specify authentication parameters. ## Prometheus Configuration The snmp exporter needs to be passed the address as a parameter, this can be done with relabelling. Example config: ```YAML scrape_configs: - job_name: 'snmp' static_configs: - targets: - 192.168.1.2 # SNMP device. metrics_path: /snmp params: module: [if_mib] relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: 127.0.0.1:9116 # The SNMP exporter's real hostname:port. ``` This setup allows Prometheus to provide scheduling and service discovery, as unlike all other exporters running an exporter on the machine from which we are getting the metrics from is not possible. ## Large counter value handling In order to provide accurate counters for large Counter64 values, the exporter will automatically wrap the value every 2^53 to avoid 64-bit float rounding. To disable this feature, use the command line flag `--no-snmp.wrap-large-counters`. prometheus-snmp-exporter-0.16.1+ds/VERSION000066400000000000000000000000071360361176200202670ustar00rootroot000000000000000.16.1 prometheus-snmp-exporter-0.16.1+ds/collector.go000066400000000000000000000543541360361176200215520ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "encoding/binary" "fmt" "net" "strconv" "strings" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/soniah/gosnmp" "gopkg.in/alecthomas/kingpin.v2" "github.com/prometheus/snmp_exporter/config" ) var ( snmpUnexpectedPduType = prometheus.NewCounter( prometheus.CounterOpts{ Name: "snmp_unexpected_pdu_type_total", Help: "Unexpected Go types in a PDU.", }, ) // 64-bit float mantissa: https://en.wikipedia.org/wiki/Double-precision_floating-point_format float64Mantissa uint64 = 9007199254740992 wrapCounters = kingpin.Flag("snmp.wrap-large-counters", "Wrap 64-bit counters to avoid floating point rounding.").Default("true").Bool() ) func init() { prometheus.MustRegister(snmpUnexpectedPduType) } // Types preceded by an enum with their actual type. var combinedTypeMapping = map[string]map[int]string{ "InetAddress": { 1: "InetAddressIPv4", 2: "InetAddressIPv6", }, "InetAddressMissingSize": { 1: "InetAddressIPv4", 2: "InetAddressIPv6", }, "LldpPortId": { 1: "DisplayString", 2: "DisplayString", 3: "PhysAddress48", 5: "DisplayString", 7: "DisplayString", }, } func oidToList(oid string) []int { result := []int{} for _, x := range strings.Split(oid, ".") { o, _ := strconv.Atoi(x) result = append(result, o) } return result } func listToOid(l []int) string { var result []string for _, o := range l { result = append(result, strconv.Itoa(o)) } return strings.Join(result, ".") } func ScrapeTarget(target string, config *config.Module, logger log.Logger) ([]gosnmp.SnmpPDU, error) { // Set the options. snmp := gosnmp.GoSNMP{} snmp.MaxRepetitions = config.WalkParams.MaxRepetitions // User specifies timeout of each retry attempt but GoSNMP expects total timeout for all attempts. snmp.Retries = config.WalkParams.Retries snmp.Timeout = config.WalkParams.Timeout * time.Duration(snmp.Retries) snmp.Target = target snmp.Port = 161 if host, port, err := net.SplitHostPort(target); err == nil { snmp.Target = host p, err := strconv.Atoi(port) if err != nil { return nil, fmt.Errorf("error converting port number to int for target %s: %s", target, err) } snmp.Port = uint16(p) } // Configure auth. config.WalkParams.ConfigureSNMP(&snmp) // Do the actual walk. err := snmp.Connect() if err != nil { return nil, fmt.Errorf("error connecting to target %s: %s", target, err) } defer snmp.Conn.Close() result := []gosnmp.SnmpPDU{} getOids := config.Get maxOids := int(config.WalkParams.MaxRepetitions) // Max Repetition can be 0, maxOids cannot. SNMPv1 can only report one OID error per call. if maxOids == 0 || snmp.Version == gosnmp.Version1 { maxOids = 1 } for len(getOids) > 0 { oids := len(getOids) if oids > maxOids { oids = maxOids } level.Debug(logger).Log("msg", "Getting OIDs", "oids", oids) getStart := time.Now() packet, err := snmp.Get(getOids[:oids]) if err != nil { return nil, fmt.Errorf("error getting target %s: %s", snmp.Target, err) } level.Debug(logger).Log("msg", "Get of OIDs completed", "oids", oids, "duration_seconds", time.Since(getStart)) // SNMPv1 will return packet error for unsupported OIDs. if packet.Error == gosnmp.NoSuchName && snmp.Version == gosnmp.Version1 { level.Debug(logger).Log("msg", "OID not supported by target", "oids", getOids[0]) getOids = getOids[oids:] continue } // Response received with errors. // TODO: "stringify" gosnmp errors instead of showing error code. if packet.Error != gosnmp.NoError { return nil, fmt.Errorf("error reported by target %s: Error Status %d", snmp.Target, packet.Error) } for _, v := range packet.Variables { if v.Type == gosnmp.NoSuchObject || v.Type == gosnmp.NoSuchInstance { level.Debug(logger).Log("msg", "OID not supported by target", "oids", v.Name) continue } result = append(result, v) } getOids = getOids[oids:] } for _, subtree := range config.Walk { var pdus []gosnmp.SnmpPDU level.Debug(logger).Log("msg", "Walking subtree", "oid", subtree) walkStart := time.Now() if snmp.Version == gosnmp.Version1 { pdus, err = snmp.WalkAll(subtree) } else { pdus, err = snmp.BulkWalkAll(subtree) } if err != nil { return nil, fmt.Errorf("error walking target %s: %s", snmp.Target, err) } level.Debug(logger).Log("msg", "Walk of subtree completed", "oid", subtree, "duration_seconds", time.Since(walkStart)) result = append(result, pdus...) } return result, nil } type MetricNode struct { metric *config.Metric children map[int]*MetricNode } // Build a tree of metrics from the config, for fast lookup when there's lots of them. func buildMetricTree(metrics []*config.Metric) *MetricNode { metricTree := &MetricNode{children: map[int]*MetricNode{}} for _, metric := range metrics { head := metricTree for _, o := range oidToList(metric.Oid) { _, ok := head.children[o] if !ok { head.children[o] = &MetricNode{children: map[int]*MetricNode{}} } head = head.children[o] } head.metric = metric } return metricTree } type collector struct { target string module *config.Module logger log.Logger } // Describe implements Prometheus.Collector. func (c collector) Describe(ch chan<- *prometheus.Desc) { ch <- prometheus.NewDesc("dummy", "dummy", nil, nil) } // Collect implements Prometheus.Collector. func (c collector) Collect(ch chan<- prometheus.Metric) { start := time.Now() pdus, err := ScrapeTarget(c.target, c.module, c.logger) if err != nil { level.Info(c.logger).Log("msg", "Error scraping target", "err", err) ch <- prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error scraping target", nil, nil), err) return } ch <- prometheus.MustNewConstMetric( prometheus.NewDesc("snmp_scrape_walk_duration_seconds", "Time SNMP walk/bulkwalk took.", nil, nil), prometheus.GaugeValue, time.Since(start).Seconds()) ch <- prometheus.MustNewConstMetric( prometheus.NewDesc("snmp_scrape_pdus_returned", "PDUs returned from walk.", nil, nil), prometheus.GaugeValue, float64(len(pdus))) oidToPdu := make(map[string]gosnmp.SnmpPDU, len(pdus)) for _, pdu := range pdus { oidToPdu[pdu.Name[1:]] = pdu } metricTree := buildMetricTree(c.module.Metrics) // Look for metrics that match each pdu. PduLoop: for oid, pdu := range oidToPdu { head := metricTree oidList := oidToList(oid) for i, o := range oidList { var ok bool head, ok = head.children[o] if !ok { continue PduLoop } if head.metric != nil { // Found a match. samples := pduToSamples(oidList[i+1:], &pdu, head.metric, oidToPdu, c.logger) for _, sample := range samples { ch <- sample } break } } } ch <- prometheus.MustNewConstMetric( prometheus.NewDesc("snmp_scrape_duration_seconds", "Total SNMP time scrape took (walk and processing).", nil, nil), prometheus.GaugeValue, time.Since(start).Seconds()) } func getPduValue(pdu *gosnmp.SnmpPDU) float64 { switch pdu.Type { case gosnmp.Counter64: if *wrapCounters { // Wrap by 2^53. return float64(gosnmp.ToBigInt(pdu.Value).Uint64() % float64Mantissa) } else { return float64(gosnmp.ToBigInt(pdu.Value).Uint64()) } case gosnmp.OpaqueFloat: return float64(pdu.Value.(float32)) case gosnmp.OpaqueDouble: return pdu.Value.(float64) default: return float64(gosnmp.ToBigInt(pdu.Value).Int64()) } } // parseDateAndTime extracts a UNIX timestamp from an RFC 2579 DateAndTime. func parseDateAndTime(pdu *gosnmp.SnmpPDU) (float64, error) { var ( v []byte tz *time.Location err error ) // DateAndTime should be a slice of bytes. switch pduType := pdu.Value.(type) { case []byte: v = pdu.Value.([]byte) default: return 0, fmt.Errorf("invalid DateAndTime type %v", pduType) } pduLength := len(v) // DateAndTime can be 8 or 11 bytes depending if the time zone is included. switch pduLength { case 8: // No time zone included, assume UTC. tz = time.UTC case 11: // Extract the timezone from the last 3 bytes. locString := fmt.Sprintf("%s%02d%02d", string(v[8]), v[9], v[10]) loc, err := time.Parse("-0700", locString) if err != nil { return 0, fmt.Errorf("error parsing location string: %q, error: %s", locString, err) } tz = loc.Location() default: return 0, fmt.Errorf("invalid DateAndTime length %v", pduLength) } if err != nil { return 0, fmt.Errorf("unable to parse DateAndTime %q, error: %s", v, err) } // Build the date from the various fields and time zone. t := time.Date( int(binary.BigEndian.Uint16(v[0:2])), time.Month(v[2]), int(v[3]), int(v[4]), int(v[5]), int(v[6]), int(v[7])*1e+8, tz) return float64(t.Unix()), nil } func pduToSamples(indexOids []int, pdu *gosnmp.SnmpPDU, metric *config.Metric, oidToPdu map[string]gosnmp.SnmpPDU, logger log.Logger) []prometheus.Metric { var err error // The part of the OID that is the indexes. labels := indexesToLabels(indexOids, metric, oidToPdu) value := getPduValue(pdu) t := prometheus.UntypedValue labelnames := make([]string, 0, len(labels)+1) labelvalues := make([]string, 0, len(labels)+1) for k, v := range labels { labelnames = append(labelnames, k) labelvalues = append(labelvalues, v) } switch metric.Type { case "counter": t = prometheus.CounterValue case "gauge": t = prometheus.GaugeValue case "Float", "Double": t = prometheus.GaugeValue case "DateAndTime": t = prometheus.GaugeValue value, err = parseDateAndTime(pdu) if err != nil { level.Debug(logger).Log("msg", "Error parsing DateAndTime", "err", err) return []prometheus.Metric{} } case "EnumAsInfo": return enumAsInfo(metric, int(value), labelnames, labelvalues) case "EnumAsStateSet": return enumAsStateSet(metric, int(value), labelnames, labelvalues) case "Bits": return bits(metric, pdu.Value, labelnames, labelvalues) default: // It's some form of string. t = prometheus.GaugeValue value = 1.0 metricType := metric.Type if typeMapping, ok := combinedTypeMapping[metricType]; ok { // Lookup associated sub type in previous object. oids := strings.Split(metric.Oid, ".") i, _ := strconv.Atoi(oids[len(oids)-1]) oids[len(oids)-1] = strconv.Itoa(i - 1) prevOid := fmt.Sprintf("%s.%s", strings.Join(oids, "."), listToOid(indexOids)) if prevPdu, ok := oidToPdu[prevOid]; ok { val := int(getPduValue(&prevPdu)) if t, ok := typeMapping[val]; ok { metricType = t } else { metricType = "OctetString" level.Debug(logger).Log("msg", "Unable to handle type value", "value", val, "oid", prevOid, "metric", metric.Name) } } else { metricType = "OctetString" level.Debug(logger).Log("msg", "Unable to find type at oid for metric", "oid", prevOid, "metric", metric.Name) } } if len(metric.RegexpExtracts) > 0 { return applyRegexExtracts(metric, pduValueAsString(pdu, metricType), labelnames, labelvalues, logger) } // For strings we put the value as a label with the same name as the metric. // If the name is already an index, we do not need to set it again. if _, ok := labels[metric.Name]; !ok { labelnames = append(labelnames, metric.Name) labelvalues = append(labelvalues, pduValueAsString(pdu, metricType)) } } sample, err := prometheus.NewConstMetric(prometheus.NewDesc(metric.Name, metric.Help, labelnames, nil), t, value, labelvalues...) if err != nil { sample = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric", nil, nil), fmt.Errorf("error for metric %s with labels %v from indexOids %v: %v", metric.Name, labelvalues, indexOids, err)) } return []prometheus.Metric{sample} } func applyRegexExtracts(metric *config.Metric, pduValue string, labelnames, labelvalues []string, logger log.Logger) []prometheus.Metric { results := []prometheus.Metric{} for name, strMetricSlice := range metric.RegexpExtracts { for _, strMetric := range strMetricSlice { indexes := strMetric.Regex.FindStringSubmatchIndex(pduValue) if indexes == nil { level.Debug(logger).Log("msg", "No match found for regexp", "metric", metric.Name, "value", pduValue, "regex", strMetric.Regex.String()) continue } res := strMetric.Regex.ExpandString([]byte{}, strMetric.Value, pduValue, indexes) v, err := strconv.ParseFloat(string(res), 64) if err != nil { level.Debug(logger).Log("msg", "Error parsing float64 from value", "metric", metric.Name, "value", pduValue, "regex", strMetric.Regex.String(), "extracted_value", res) continue } newMetric, err := prometheus.NewConstMetric(prometheus.NewDesc(metric.Name+name, metric.Help+" (regex extracted)", labelnames, nil), prometheus.GaugeValue, v, labelvalues...) if err != nil { newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for regex_extract", nil, nil), fmt.Errorf("error for metric %s with labels %v: %v", metric.Name+name, labelvalues, err)) } results = append(results, newMetric) break } } return results } func enumAsInfo(metric *config.Metric, value int, labelnames, labelvalues []string) []prometheus.Metric { // Lookup enum, default to the value. state, ok := metric.EnumValues[int(value)] if !ok { state = strconv.Itoa(int(value)) } labelnames = append(labelnames, metric.Name) labelvalues = append(labelvalues, state) newMetric, err := prometheus.NewConstMetric(prometheus.NewDesc(metric.Name+"_info", metric.Help+" (EnumAsInfo)", labelnames, nil), prometheus.GaugeValue, 1.0, labelvalues...) if err != nil { newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for EnumAsInfo", nil, nil), fmt.Errorf("error for metric %s with labels %v: %v", metric.Name, labelvalues, err)) } return []prometheus.Metric{newMetric} } func enumAsStateSet(metric *config.Metric, value int, labelnames, labelvalues []string) []prometheus.Metric { labelnames = append(labelnames, metric.Name) results := []prometheus.Metric{} state, ok := metric.EnumValues[value] if !ok { // Fallback to using the value. state = strconv.Itoa(value) } newMetric, err := prometheus.NewConstMetric(prometheus.NewDesc(metric.Name, metric.Help+" (EnumAsStateSet)", labelnames, nil), prometheus.GaugeValue, 1.0, append(labelvalues, state)...) if err != nil { newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for EnumAsStateSet", nil, nil), fmt.Errorf("error for metric %s with labels %v: %v", metric.Name, labelvalues, err)) } results = append(results, newMetric) for k, v := range metric.EnumValues { if k == value { continue } newMetric, err := prometheus.NewConstMetric(prometheus.NewDesc(metric.Name, metric.Help+" (EnumAsStateSet)", labelnames, nil), prometheus.GaugeValue, 0.0, append(labelvalues, v)...) if err != nil { newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for EnumAsStateSet", nil, nil), fmt.Errorf("error for metric %s with labels %v: %v", metric.Name, labelvalues, err)) } results = append(results, newMetric) } return results } func bits(metric *config.Metric, value interface{}, labelnames, labelvalues []string) []prometheus.Metric { bytes, ok := value.([]byte) if !ok { return []prometheus.Metric{prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "BITS type was not a BISTRING on the wire.", nil, nil), fmt.Errorf("error for metric %s with labels %v: %T", metric.Name, labelvalues, value))} } labelnames = append(labelnames, metric.Name) results := []prometheus.Metric{} for k, v := range metric.EnumValues { bit := 0.0 // Most significant byte most significant bit, then most significant byte 2nd most significant bit etc. if k < len(bytes)*8 { if (bytes[k/8] & (128 >> (k % 8))) != 0 { bit = 1.0 } } newMetric, err := prometheus.NewConstMetric(prometheus.NewDesc(metric.Name, metric.Help+" (Bits)", labelnames, nil), prometheus.GaugeValue, bit, append(labelvalues, v)...) if err != nil { newMetric = prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error calling NewConstMetric for Bits", nil, nil), fmt.Errorf("error for metric %s with labels %v: %v", metric.Name, labelvalues, err)) } results = append(results, newMetric) } return results } // Right pad oid with zeros, and split at the given point. // Some routers exclude trailing 0s in responses. func splitOid(oid []int, count int) ([]int, []int) { head := make([]int, count) tail := []int{} for i, v := range oid { if i < count { head[i] = v } else { tail = append(tail, v) } } return head, tail } // This mirrors decodeValue in gosnmp's helper.go. func pduValueAsString(pdu *gosnmp.SnmpPDU, typ string) string { switch pdu.Value.(type) { case int: return strconv.Itoa(pdu.Value.(int)) case uint: return strconv.FormatUint(uint64(pdu.Value.(uint)), 10) case uint64: return strconv.FormatUint(pdu.Value.(uint64), 10) case float32: return strconv.FormatFloat(float64(pdu.Value.(float32)), 'f', -1, 32) case float64: return strconv.FormatFloat(pdu.Value.(float64), 'f', -1, 64) case string: if pdu.Type == gosnmp.ObjectIdentifier { // Trim leading period. return pdu.Value.(string)[1:] } // DisplayString. return pdu.Value.(string) case []byte: if typ == "" { typ = "OctetString" } // Reuse the OID index parsing code. parts := make([]int, len(pdu.Value.([]byte))) for i, o := range pdu.Value.([]byte) { parts[i] = int(o) } if typ == "OctetString" || typ == "DisplayString" { // Prepend the length, as it is explicit in an index. parts = append([]int{len(pdu.Value.([]byte))}, parts...) } str, _, _ := indexOidsAsString(parts, typ, 0, false) return str case nil: return "" default: // This shouldn't happen. snmpUnexpectedPduType.Inc() return fmt.Sprintf("%s", pdu.Value) } } // Convert oids to a string index value. // // Returns the string, the oids that were used and the oids left over. func indexOidsAsString(indexOids []int, typ string, fixedSize int, implied bool) (string, []int, []int) { if typeMapping, ok := combinedTypeMapping[typ]; ok { subOid, valueOids := splitOid(indexOids, 2) if typ == "InetAddressMissingSize" { // The size of the main index value is missing. subOid, valueOids = splitOid(indexOids, 1) } var str string var used, remaining []int if t, ok := typeMapping[subOid[0]]; ok { str, used, remaining = indexOidsAsString(valueOids, t, 0, false) return str, append(subOid, used...), remaining } if typ == "InetAddressMissingSize" { // We don't know the size, so pass everything remaining. return indexOidsAsString(indexOids, "OctetString", 0, true) } // The 2nd oid is the length. return indexOidsAsString(indexOids, "OctetString", subOid[1]+2, false) } switch typ { case "Integer32", "Integer", "gauge", "counter": // Extract the oid for this index, and keep the remainder for the next index. subOid, indexOids := splitOid(indexOids, 1) return fmt.Sprintf("%d", subOid[0]), subOid, indexOids case "PhysAddress48": subOid, indexOids := splitOid(indexOids, 6) parts := make([]string, 6) for i, o := range subOid { parts[i] = fmt.Sprintf("%02X", o) } return strings.Join(parts, ":"), subOid, indexOids case "OctetString": var subOid []int // The length of fixed size indexes come from the MIB. // For varying size, we read it from the first oid. length := fixedSize if implied { length = len(indexOids) } if length == 0 { subOid, indexOids = splitOid(indexOids, 1) length = subOid[0] } content, indexOids := splitOid(indexOids, length) subOid = append(subOid, content...) parts := make([]byte, length) for i, o := range content { parts[i] = byte(o) } if len(parts) == 0 { return "", subOid, indexOids } else { return fmt.Sprintf("0x%X", string(parts)), subOid, indexOids } case "DisplayString": var subOid []int length := fixedSize if implied { length = len(indexOids) } if length == 0 { subOid, indexOids = splitOid(indexOids, 1) length = subOid[0] } content, indexOids := splitOid(indexOids, length) subOid = append(subOid, content...) parts := make([]byte, length) for i, o := range content { parts[i] = byte(o) } // ASCII, so can convert staight to utf-8. return string(parts), subOid, indexOids case "InetAddressIPv4": subOid, indexOids := splitOid(indexOids, 4) parts := make([]string, 4) for i, o := range subOid { parts[i] = strconv.Itoa(o) } return strings.Join(parts, "."), subOid, indexOids case "InetAddressIPv6": subOid, indexOids := splitOid(indexOids, 16) parts := make([]interface{}, 16) for i, o := range subOid { parts[i] = o } return fmt.Sprintf("%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X", parts...), subOid, indexOids default: panic(fmt.Sprintf("Unknown index type %s", typ)) return "", nil, nil } } func indexesToLabels(indexOids []int, metric *config.Metric, oidToPdu map[string]gosnmp.SnmpPDU) map[string]string { labels := map[string]string{} labelOids := map[string][]int{} // Covert indexes to useful strings. for _, index := range metric.Indexes { str, subOid, remainingOids := indexOidsAsString(indexOids, index.Type, index.FixedSize, index.Implied) // The labelvalue is the text form of the index oids. labels[index.Labelname] = str // Save its oid in case we need it for lookups. labelOids[index.Labelname] = subOid // For the next iteration. indexOids = remainingOids } // Perform lookups. for _, lookup := range metric.Lookups { if len(lookup.Labels) == 0 { delete(labels, lookup.Labelname) continue } oid := lookup.Oid for _, label := range lookup.Labels { oid = fmt.Sprintf("%s.%s", oid, listToOid(labelOids[label])) } if pdu, ok := oidToPdu[oid]; ok { labels[lookup.Labelname] = pduValueAsString(&pdu, lookup.Type) } else { labels[lookup.Labelname] = "" } } return labels } prometheus-snmp-exporter-0.16.1+ds/collector_test.go000066400000000000000000000740021360361176200226010ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "errors" "reflect" "regexp" "testing" "github.com/go-kit/kit/log" "github.com/prometheus/client_model/go" "github.com/soniah/gosnmp" kingpin "gopkg.in/alecthomas/kingpin.v2" "github.com/prometheus/snmp_exporter/config" ) func TestPduToSample(t *testing.T) { cases := []struct { pdu *gosnmp.SnmpPDU indexOids []int metric *config.Metric oidToPdu map[string]gosnmp.SnmpPDU expectedMetrics []string shouldErr bool }{ { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Value: "SomeStringValue", }, indexOids: []int{}, metric: &config.Metric{ Name: "TestMetricName", Oid: "1.1.1.1.1", Help: "HelpText", RegexpExtracts: map[string][]config.RegexpExtract{ "Extension": []config.RegexpExtract{ { Regex: config.Regexp{ regexp.MustCompile(".*"), }, Value: "5", }, }, }, }, oidToPdu: make(map[string]gosnmp.SnmpPDU), expectedMetrics: []string{ `Desc{fqName: "TestMetricNameExtension", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge: `, }, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Value: "SomeStringValue", }, indexOids: []int{}, metric: &config.Metric{ Name: "TestMetricName", Oid: "1.1.1.1.1", Help: "HelpText", RegexpExtracts: map[string][]config.RegexpExtract{ "Extension": []config.RegexpExtract{ { Regex: config.Regexp{ regexp.MustCompile(".*"), }, Value: "", }, }, }, }, expectedMetrics: []string{}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Value: "SomeStringValue", }, indexOids: []int{}, metric: &config.Metric{ Name: "TestMetricName", Oid: "1.1.1.1.1", Help: "HelpText", RegexpExtracts: map[string][]config.RegexpExtract{ "Extension": []config.RegexpExtract{ { Regex: config.Regexp{ regexp.MustCompile("(will_not_match)"), }, Value: "", }, }, }, }, expectedMetrics: []string{}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Value: 2, }, indexOids: []int{}, metric: &config.Metric{ Name: "TestMetricName", Oid: "1.1.1.1.1", Help: "HelpText", RegexpExtracts: map[string][]config.RegexpExtract{ "Status": []config.RegexpExtract{ { Regex: config.Regexp{ regexp.MustCompile(".*"), }, Value: "5", }, }, }, }, expectedMetrics: []string{ `Desc{fqName: "TestMetricNameStatus", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge: `, }, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Value: "Test value 4.42 123 999", }, indexOids: []int{}, metric: &config.Metric{ Name: "TestMetricName", Oid: "1.1.1.1.1", Help: "HelpText", RegexpExtracts: map[string][]config.RegexpExtract{ "Blank": []config.RegexpExtract{ { Regex: config.Regexp{ regexp.MustCompile("^XXXX$"), }, Value: "4", }, }, "Extension": []config.RegexpExtract{ { Regex: config.Regexp{ regexp.MustCompile(".*"), }, Value: "5", }, }, "MultipleRegexes": []config.RegexpExtract{ { Regex: config.Regexp{ regexp.MustCompile("^XXXX$"), }, Value: "123", }, { Regex: config.Regexp{ regexp.MustCompile("123.*"), }, Value: "999", }, { Regex: config.Regexp{ regexp.MustCompile(".*"), }, Value: "777", }, }, "Template": []config.RegexpExtract{ { Regex: config.Regexp{ regexp.MustCompile("([0-9].[0-9]+)"), }, Value: "$1", }, }, }, }, oidToPdu: make(map[string]gosnmp.SnmpPDU), expectedMetrics: []string{ `Desc{fqName: "TestMetricNameExtension", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge: `, `Desc{fqName: "TestMetricNameMultipleRegexes", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge: `, `Desc{fqName: "TestMetricNameTemplate", help: "HelpText (regex extracted)", constLabels: {}, variableLabels: []} gauge: `, }, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Type: gosnmp.Integer, Value: 2, }, indexOids: []int{}, metric: &config.Metric{ Name: "test_metric", Oid: "1.1.1.1.1", Type: "counter", Help: "Help string", }, oidToPdu: make(map[string]gosnmp.SnmpPDU), expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []} counter: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Type: gosnmp.Integer, Value: 2, }, indexOids: []int{}, metric: &config.Metric{ Name: "test_metric", Oid: "1.1.1.1.1", Type: "gauge", Help: "Help string", }, oidToPdu: make(map[string]gosnmp.SnmpPDU), expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []} gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Type: gosnmp.Integer, Value: -2, }, indexOids: []int{}, metric: &config.Metric{ Name: "test_metric", Oid: "1.1.1.1.1", Help: "Help string", }, oidToPdu: make(map[string]gosnmp.SnmpPDU), expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Type: gosnmp.OpaqueFloat, Value: float32(3.0), }, indexOids: []int{}, metric: &config.Metric{ Name: "test_metric", Oid: "1.1.1.1.1", Type: "gauge", Help: "Help string", }, oidToPdu: make(map[string]gosnmp.SnmpPDU), expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []} gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Type: gosnmp.OpaqueDouble, Value: float64(3.0), }, indexOids: []int{}, metric: &config.Metric{ Name: "test_metric", Oid: "1.1.1.1.1", Type: "gauge", Help: "Help string", }, oidToPdu: make(map[string]gosnmp.SnmpPDU), expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: []} gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Type: gosnmp.Integer, Value: 3, }, indexOids: []int{2, 65, 65}, metric: &config.Metric{ Name: "test_metric", Oid: "1.1.1.1.1", Type: "gauge", Help: "Help string", Indexes: []*config.Index{{Labelname: "foo", Type: "DisplayString"}}, }, oidToPdu: make(map[string]gosnmp.SnmpPDU), expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [foo]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Type: gosnmp.Integer, Value: 3, }, indexOids: []int{2, 65, 255}, metric: &config.Metric{ Name: "test_metric", Oid: "1.1.1.1.1", Type: "gauge", Help: "Help string", Indexes: []*config.Index{{Labelname: "foo", Type: "DisplayString"}}, }, oidToPdu: make(map[string]gosnmp.SnmpPDU), shouldErr: true, // Invalid ASCII/UTF-8 string. }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1.1.1.1", Type: gosnmp.Integer, Value: 3, }, indexOids: []int{2, 65, 255}, metric: &config.Metric{ Name: "test_metric", Oid: "1.1.1.1.1", Type: "gauge", Help: "Help string", Indexes: []*config.Index{{Labelname: "foo", Type: "DisplayString"}}, RegexpExtracts: map[string][]config.RegexpExtract{ "": []config.RegexpExtract{{Value: "1", Regex: config.Regexp{regexp.MustCompile(".*")}}}, }, }, oidToPdu: make(map[string]gosnmp.SnmpPDU), shouldErr: true, // Invalid ASCII/UTF-8 string. }, { pdu: &gosnmp.SnmpPDU{ Name: "1.42.2", Value: []byte{4, 5, 6, 7}, }, indexOids: []int{2}, metric: &config.Metric{ Name: "test_metric", Oid: "1.42", Type: "InetAddress", Help: "Help string", }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 1}}, expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.42.2", Value: []byte{4, 5, 6, 7}, }, indexOids: []int{2}, metric: &config.Metric{ Name: "test_metric", Oid: "1.42", Type: "InetAddressMissingSize", Help: "Help string", }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 1}}, expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.42.2", Value: []byte{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, }, indexOids: []int{2}, metric: &config.Metric{ Name: "test_metric", Oid: "1.42", Type: "InetAddress", Help: "Help string", }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 2}}, expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.42.2", Value: []byte{4, 5, 6, 7, 8}, }, indexOids: []int{2}, metric: &config.Metric{ Name: "test_metric", Oid: "1.42", Type: "InetAddress", Help: "Help string", }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 3}}, expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.42.2", Value: []byte{4, 5, 6, 7}, }, indexOids: []int{2}, metric: &config.Metric{ Name: "test_metric", Oid: "1.42", Type: "InetAddress", Help: "Help string", }, oidToPdu: make(map[string]gosnmp.SnmpPDU), expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.42.2", Value: []byte{4, 5, 6, 7, 8, 9}, }, indexOids: []int{2}, metric: &config.Metric{ Name: "test_metric", Oid: "1.42", Type: "LldpPortId", Help: "Help string", }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.41.2": gosnmp.SnmpPDU{Value: 3}}, expectedMetrics: []string{`Desc{fqName: "test_metric", help: "Help string", constLabels: {}, variableLabels: [test_metric]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1", Type: gosnmp.Integer, Value: 2, }, metric: &config.Metric{ Name: "test_metric", Oid: "1.1", Type: "EnumAsInfo", Help: "Help string", EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz"}, }, expectedMetrics: []string{`Desc{fqName: "test_metric_info", help: "Help string (EnumAsInfo)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1", Type: gosnmp.Integer, Value: 3, }, metric: &config.Metric{ Name: "test_metric", Oid: "1.1", Type: "EnumAsInfo", Help: "Help string", EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz"}, }, expectedMetrics: []string{`Desc{fqName: "test_metric_info", help: "Help string (EnumAsInfo)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `}, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1", Type: gosnmp.Integer, Value: 2, }, metric: &config.Metric{ Name: "test_metric", Oid: "1.1", Type: "EnumAsStateSet", Help: "Help string", EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz"}, }, expectedMetrics: []string{ `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, }, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1", Type: gosnmp.Integer, Value: 3, }, metric: &config.Metric{ Name: "test_metric", Oid: "1.1", Type: "EnumAsStateSet", Help: "Help string", EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz"}, }, expectedMetrics: []string{ `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (EnumAsStateSet)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, }, }, { pdu: &gosnmp.SnmpPDU{ Name: "1.1", Type: gosnmp.Integer, Value: []byte{1<<7 + 1<<6, 1 << 7, 1 << 0}, }, metric: &config.Metric{ Name: "test_metric", Oid: "1.1", Type: "Bits", Help: "Help string", EnumValues: map[int]string{0: "foo", 1: "bar", 2: "baz", 8: "byte2msb", 15: "byte2lsb", 16: "byte3msb", 23: "byte3lsb", 24: "missing"}, }, expectedMetrics: []string{ `Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, `Desc{fqName: "test_metric", help: "Help string (Bits)", constLabels: {}, variableLabels: [test_metric]} label: gauge: `, }, }, } for _, c := range cases { metrics := pduToSamples(c.indexOids, c.pdu, c.metric, c.oidToPdu, log.NewNopLogger()) metric := &io_prometheus_client.Metric{} expected := map[string]struct{}{} for _, e := range c.expectedMetrics { expected[e] = struct{}{} } errHappened := false for _, m := range metrics { err := m.Write(metric) if err != nil { if c.shouldErr { errHappened = true continue } else { t.Fatalf("Error writing metric: %v", err) } } got := m.Desc().String() + " " + metric.String() if _, ok := expected[got]; !ok { t.Errorf("Unexpected metric: got %v", got) } else { delete(expected, got) } } for e := range expected { t.Errorf("Expected metric %v, but was not returned.", e) } if !errHappened && c.shouldErr { t.Fatalf("Was expecting error, but none returned.") } } } func TestGetPduValue(t *testing.T) { pdu := &gosnmp.SnmpPDU{ Value: uint64(1 << 63), Type: gosnmp.Counter64, } value := getPduValue(pdu) if value <= 0 { t.Fatalf("Got negative value for PDU value type Counter64: %v", value) } } func TestGetPduLargeValue(t *testing.T) { _, err := kingpin.CommandLine.Parse([]string{}) if err != nil { t.Fatal(err) } pdu := &gosnmp.SnmpPDU{ Value: uint64(19007199254740992), Type: gosnmp.Counter64, } value := getPduValue(pdu) if value != 992800745259008.0 { t.Fatalf("Got incorrect counter wrapping for Counter64: %v", value) } _, err = kingpin.CommandLine.Parse([]string{"--no-snmp.wrap-large-counters"}) if err != nil { t.Fatal(err) } pdu = &gosnmp.SnmpPDU{ Value: uint64(19007199254740992), Type: gosnmp.Counter64, } value = getPduValue(pdu) if value != 19007199254740990.0 { t.Fatalf("Got incorrect rounded float for Counter64: %v", value) } } func TestOidToList(t *testing.T) { cases := []struct { oid string result []int }{ { oid: "1", result: []int{1}, }, { oid: "1.2.3.4", result: []int{1, 2, 3, 4}, }, } for _, c := range cases { got := oidToList(c.oid) if !reflect.DeepEqual(got, c.result) { t.Errorf("oidToList(%v): got %v, want %v", c.oid, got, c.result) } } } func TestSplitOid(t *testing.T) { cases := []struct { oid []int count int resultHead []int resultTail []int }{ { oid: []int{1, 2, 3, 4}, count: 2, resultHead: []int{1, 2}, resultTail: []int{3, 4}, }, { oid: []int{1, 2}, count: 4, resultHead: []int{1, 2, 0, 0}, resultTail: []int{}, }, { oid: []int{}, count: 2, resultHead: []int{0, 0}, resultTail: []int{}, }, } for _, c := range cases { head, tail := splitOid(c.oid, c.count) if !reflect.DeepEqual(head, c.resultHead) || !reflect.DeepEqual(tail, c.resultTail) { t.Errorf("splitOid(%v, %d): got [%v, %v], want [%v, %v]", c.oid, c.count, head, tail, c.resultHead, c.resultTail) } } } func TestPduValueAsString(t *testing.T) { cases := []struct { pdu *gosnmp.SnmpPDU typ string result string }{ { pdu: &gosnmp.SnmpPDU{Value: int(-1)}, result: "-1", }, { pdu: &gosnmp.SnmpPDU{Value: uint(1)}, result: "1", }, { pdu: &gosnmp.SnmpPDU{Value: uint64(1)}, result: "1", }, { pdu: &gosnmp.SnmpPDU{Value: ".1.2.3.4", Type: gosnmp.ObjectIdentifier}, result: "1.2.3.4", }, { pdu: &gosnmp.SnmpPDU{Value: "1.2.3.4", Type: gosnmp.IPAddress}, result: "1.2.3.4", }, { pdu: &gosnmp.SnmpPDU{Value: []byte{}}, result: "", }, { pdu: &gosnmp.SnmpPDU{Value: []byte{65, 66}}, typ: "DisplayString", result: "AB", }, { pdu: &gosnmp.SnmpPDU{Value: []byte{127, 128, 255, 0}}, result: "0x7F80FF00", }, { pdu: &gosnmp.SnmpPDU{Value: []byte{127, 128, 255, 0}}, typ: "OctetString", result: "0x7F80FF00", }, { pdu: &gosnmp.SnmpPDU{Value: []byte{1, 2, 3, 4}}, typ: "InetAddressIPv4", result: "1.2.3.4", }, { pdu: &gosnmp.SnmpPDU{Value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, typ: "InetAddressIPv6", result: "0102:0304:0506:0708:090A:0B0C:0D0E:0F10", }, { pdu: &gosnmp.SnmpPDU{Value: nil}, result: "", }, { pdu: &gosnmp.SnmpPDU{Value: float32(10.1), Type: gosnmp.OpaqueFloat}, result: "10.1", }, { pdu: &gosnmp.SnmpPDU{Value: 10.1, Type: gosnmp.OpaqueDouble}, result: "10.1", }, } for _, c := range cases { got := pduValueAsString(c.pdu, c.typ) if !reflect.DeepEqual(got, c.result) { t.Errorf("pduValueAsString(%v, %q): got %q, want %q", c.pdu, c.typ, got, c.result) } } } func TestParseDateAndTime(t *testing.T) { cases := []struct { pdu *gosnmp.SnmpPDU result float64 err error }{ // No timezone, use UTC { pdu: &gosnmp.SnmpPDU{Value: []byte{7, 226, 8, 15, 8, 1, 15, 0}}, result: 1534320075, err: nil, }, // +0200 { pdu: &gosnmp.SnmpPDU{Value: []byte{7, 226, 8, 15, 8, 1, 15, 0, 43, 2, 0}}, result: 1534312875, err: nil, }, { pdu: &gosnmp.SnmpPDU{Value: []byte{0}}, result: 0, err: errors.New("invalid DateAndTime length 1"), }, } for _, c := range cases { got, err := parseDateAndTime(c.pdu) if !reflect.DeepEqual(err, c.err) { t.Errorf("parseDateAndTime(%v) error: got %v, want %v", c.pdu, err, c.err) } if !reflect.DeepEqual(got, c.result) { t.Errorf("parseDateAndTime(%v) result: got %v, want %v", c.pdu, got, c.result) } } } func TestIndexesToLabels(t *testing.T) { cases := []struct { oid []int metric config.Metric oidToPdu map[string]gosnmp.SnmpPDU result map[string]string }{ { oid: []int{}, metric: config.Metric{}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{}, }, { oid: []int{4}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "gauge"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "4"}, }, { oid: []int{3, 4}, metric: config.Metric{ Indexes: []*config.Index{{Labelname: "a", Type: "gauge"}, {Labelname: "b", Type: "gauge"}}, Lookups: []*config.Lookup{{Labels: []string{"a", "b"}, Labelname: "l", Oid: "1.2"}}, }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.2.3.4": gosnmp.SnmpPDU{Value: "eth0"}}, result: map[string]string{"a": "3", "b": "4", "l": "eth0"}, }, { oid: []int{4}, metric: config.Metric{ Indexes: []*config.Index{{Labelname: "l", Type: "gauge"}}, Lookups: []*config.Lookup{{Labels: []string{"l"}, Labelname: "l", Oid: "1.2.3"}}, }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.2.3.4": gosnmp.SnmpPDU{Value: "eth0"}}, result: map[string]string{"l": "eth0"}, }, { oid: []int{4}, metric: config.Metric{ Indexes: []*config.Index{{Labelname: "l", Type: "gauge"}}, Lookups: []*config.Lookup{{Labels: []string{"l"}, Labelname: "l", Oid: "1.2.3", Type: "InetAddressIPv4"}}, }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.2.3.4": gosnmp.SnmpPDU{Value: []byte{5, 6, 7, 8}}}, result: map[string]string{"l": "5.6.7.8"}, }, { oid: []int{4}, metric: config.Metric{ Indexes: []*config.Index{{Labelname: "l", Type: "gauge"}}, Lookups: []*config.Lookup{{Labels: []string{"l"}, Labelname: "l", Oid: "1.2.3", Type: "InetAddressIPv6"}}, }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.2.3.4": gosnmp.SnmpPDU{Value: []byte{5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}}}, result: map[string]string{"l": "0506:0708:090A:0B0C:0D0E:0F10:1112:1314"}, }, { oid: []int{4}, metric: config.Metric{ Indexes: []*config.Index{{Labelname: "l", Type: "gauge"}}, Lookups: []*config.Lookup{{Labels: []string{"l"}, Labelname: "l", Oid: "1.2.3"}}, }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.2.3.4": gosnmp.SnmpPDU{Value: []byte{5, 6, 7, 8}}}, result: map[string]string{"l": "0x05060708"}, }, { oid: []int{4}, metric: config.Metric{ Indexes: []*config.Index{{Labelname: "l", Type: "gauge"}}, Lookups: []*config.Lookup{{Labelname: "l"}}, }, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{}, }, { oid: []int{4}, metric: config.Metric{ Indexes: []*config.Index{{Labelname: "l", Type: "gauge"}}, Lookups: []*config.Lookup{{Labels: []string{"l"}, Labelname: "l", Oid: "1.2.3"}}, }, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": ""}, }, { oid: []int{}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "gauge"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "0"}, }, { oid: []int{1, 255, 0, 0, 0, 16}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "PhysAddress48"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "01:FF:00:00:00:10"}, }, { oid: []int{3, 65, 32, 255}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "OctetString"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "0x4120FF"}, }, { oid: []int{65, 32, 255}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "OctetString", FixedSize: 3}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "0x4120FF"}, }, { oid: []int{65, 32, 255}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "OctetString", Implied: true}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "0x4120FF"}, }, { oid: []int{2, 65, 32}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "DisplayString"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "A "}, }, { oid: []int{65, 32}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "DisplayString", FixedSize: 2}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "A "}, }, { oid: []int{65, 32}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "DisplayString", Implied: true}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "A "}, }, { oid: []int{3, 65, 32, 255}, metric: config.Metric{ Indexes: []*config.Index{{Labelname: "l", Type: "OctetString"}}, Lookups: []*config.Lookup{{Labels: []string{"l"}, Labelname: "l", Oid: "1"}}, }, oidToPdu: map[string]gosnmp.SnmpPDU{"1.3.65.32.255": gosnmp.SnmpPDU{Value: "octet"}}, result: map[string]string{"l": "octet"}, }, { oid: []int{192, 168, 1, 2}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "InetAddressIPv4"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "192.168.1.2"}, }, { oid: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "InetAddressIPv6"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "0102:0304:0506:0708:090A:0B0C:0D0E:0F10"}, }, { oid: []int{1, 4, 192, 168, 1, 2}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "InetAddress"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "192.168.1.2"}, }, { oid: []int{2, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "InetAddress"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "0102:0304:0506:0708:090A:0B0C:0D0E:0F10"}, }, { oid: []int{1, 4, 192, 168, 1, 2, 2, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "a", Type: "InetAddress"}, {Labelname: "b", Type: "InetAddress"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"a": "192.168.1.2", "b": "0102:0304:0506:0708:090A:0B0C:0D0E:0F10"}, }, { oid: []int{3, 5, 192, 168, 1, 2, 5}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "InetAddress"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "0x0305C0A8010205"}, }, { oid: []int{1, 192, 168, 1, 2}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "InetAddressMissingSize"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "192.168.1.2"}, }, { oid: []int{2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "InetAddressMissingSize"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "0102:0304:0506:0708:090A:0B0C:0D0E:0F10"}, }, { oid: []int{1, 192, 168, 1, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "a", Type: "InetAddressMissingSize"}, {Labelname: "b", Type: "InetAddressMissingSize"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"a": "192.168.1.2", "b": "0102:0304:0506:0708:090A:0B0C:0D0E:0F10"}, }, { oid: []int{3, 192, 168, 1, 2, 5}, metric: config.Metric{Indexes: []*config.Index{{Labelname: "l", Type: "InetAddressMissingSize"}}}, oidToPdu: map[string]gosnmp.SnmpPDU{}, result: map[string]string{"l": "0x03C0A8010205"}, }, } for _, c := range cases { got := indexesToLabels(c.oid, &c.metric, c.oidToPdu) if !reflect.DeepEqual(got, c.result) { t.Errorf("indexesToLabels(%v, %v, %v): got %v, want %v", c.oid, c.metric, c.oidToPdu, got, c.result) } } } prometheus-snmp-exporter-0.16.1+ds/config/000077500000000000000000000000001360361176200204675ustar00rootroot00000000000000prometheus-snmp-exporter-0.16.1+ds/config/config.go000066400000000000000000000155731360361176200222760ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "fmt" "io/ioutil" "regexp" "time" "github.com/soniah/gosnmp" "gopkg.in/yaml.v2" ) func LoadFile(filename string) (*Config, error) { content, err := ioutil.ReadFile(filename) if err != nil { return nil, err } cfg := &Config{} err = yaml.UnmarshalStrict(content, cfg) if err != nil { return nil, err } return cfg, nil } var ( DefaultAuth = Auth{ Community: "public", SecurityLevel: "noAuthNoPriv", AuthProtocol: "MD5", PrivProtocol: "DES", } DefaultWalkParams = WalkParams{ Version: 2, MaxRepetitions: 25, Retries: 3, Timeout: time.Second * 20, Auth: DefaultAuth, } DefaultModule = Module{ WalkParams: DefaultWalkParams, } DefaultRegexpExtract = RegexpExtract{ Value: "$1", } ) // Config for the snmp_exporter. type Config map[string]*Module type WalkParams struct { Version int `yaml:"version,omitempty"` MaxRepetitions uint8 `yaml:"max_repetitions,omitempty"` Retries int `yaml:"retries,omitempty"` Timeout time.Duration `yaml:"timeout,omitempty"` Auth Auth `yaml:"auth,omitempty"` } type Module struct { // A list of OIDs. Walk []string `yaml:"walk,omitempty"` Get []string `yaml:"get,omitempty"` Metrics []*Metric `yaml:"metrics"` WalkParams WalkParams `yaml:",inline"` } func (c *Module) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultModule type plain Module if err := unmarshal((*plain)(c)); err != nil { return err } wp := c.WalkParams if wp.Version < 1 || wp.Version > 3 { return fmt.Errorf("SNMP version must be 1, 2 or 3. Got: %d", wp.Version) } if wp.Version == 3 { switch wp.Auth.SecurityLevel { case "authPriv": if wp.Auth.PrivPassword == "" { return fmt.Errorf("priv password is missing, required for SNMPv3 with priv") } if wp.Auth.PrivProtocol != "DES" && wp.Auth.PrivProtocol != "AES" { return fmt.Errorf("priv protocol must be DES or AES") } fallthrough case "authNoPriv": if wp.Auth.Password == "" { return fmt.Errorf("auth password is missing, required for SNMPv3 with auth") } if wp.Auth.AuthProtocol != "MD5" && wp.Auth.AuthProtocol != "SHA" { return fmt.Errorf("auth protocol must be SHA or MD5") } fallthrough case "noAuthNoPriv": if wp.Auth.Username == "" { return fmt.Errorf("auth username is missing, required for SNMPv3") } default: return fmt.Errorf("security level must be one of authPriv, authNoPriv or noAuthNoPriv") } } return nil } // ConfigureSNMP sets the various version and auth settings. func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) { switch c.Version { case 1: g.Version = gosnmp.Version1 case 2: g.Version = gosnmp.Version2c case 3: g.Version = gosnmp.Version3 } g.Community = string(c.Auth.Community) g.ContextName = c.Auth.ContextName // v3 security settings. g.SecurityModel = gosnmp.UserSecurityModel usm := &gosnmp.UsmSecurityParameters{ UserName: c.Auth.Username, } auth, priv := false, false switch c.Auth.SecurityLevel { case "noAuthNoPriv": g.MsgFlags = gosnmp.NoAuthNoPriv case "authNoPriv": g.MsgFlags = gosnmp.AuthNoPriv auth = true case "authPriv": g.MsgFlags = gosnmp.AuthPriv auth = true priv = true } if auth { usm.AuthenticationPassphrase = string(c.Auth.Password) switch c.Auth.AuthProtocol { case "SHA": usm.AuthenticationProtocol = gosnmp.SHA case "MD5": usm.AuthenticationProtocol = gosnmp.MD5 } } if priv { usm.PrivacyPassphrase = string(c.Auth.PrivPassword) switch c.Auth.PrivProtocol { case "DES": usm.PrivacyProtocol = gosnmp.DES case "AES": usm.PrivacyProtocol = gosnmp.AES } } g.SecurityParameters = usm } type Metric struct { Name string `yaml:"name"` Oid string `yaml:"oid"` Type string `yaml:"type"` Help string `yaml:"help"` Indexes []*Index `yaml:"indexes,omitempty"` Lookups []*Lookup `yaml:"lookups,omitempty"` RegexpExtracts map[string][]RegexpExtract `yaml:"regex_extracts,omitempty"` EnumValues map[int]string `yaml:"enum_values,omitempty"` } type Index struct { Labelname string `yaml:"labelname"` Type string `yaml:"type"` FixedSize int `yaml:"fixed_size,omitempty"` Implied bool `yaml:"implied,omitempty"` } type Lookup struct { Labels []string `yaml:"labels"` Labelname string `yaml:"labelname"` Oid string `yaml:"oid,omitempty"` Type string `yaml:"type,omitempty"` } // Secret is a string that must not be revealed on marshaling. type Secret string // Hack for creating snmp.yml with the secret. var ( DoNotHideSecrets = false ) // MarshalYAML implements the yaml.Marshaler interface. func (s Secret) MarshalYAML() (interface{}, error) { if DoNotHideSecrets { return string(s), nil } if s != "" { return "", nil } return nil, nil } type Auth struct { Community Secret `yaml:"community,omitempty"` SecurityLevel string `yaml:"security_level,omitempty"` Username string `yaml:"username,omitempty"` Password Secret `yaml:"password,omitempty"` AuthProtocol string `yaml:"auth_protocol,omitempty"` PrivProtocol string `yaml:"priv_protocol,omitempty"` PrivPassword Secret `yaml:"priv_password,omitempty"` ContextName string `yaml:"context_name,omitempty"` } type RegexpExtract struct { Value string `yaml:"value"` Regex Regexp `yaml:"regex"` } func (c *RegexpExtract) UnmarshalYAML(unmarshal func(interface{}) error) error { *c = DefaultRegexpExtract type plain RegexpExtract if err := unmarshal((*plain)(c)); err != nil { return err } return nil } // Regexp encapsulates a regexp.Regexp and makes it YAML marshalable. type Regexp struct { *regexp.Regexp } // MarshalYAML implements the yaml.Marshaler interface. func (re Regexp) MarshalYAML() (interface{}, error) { if re.Regexp != nil { return re.String(), nil } return nil, nil } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string if err := unmarshal(&s); err != nil { return err } regex, err := regexp.Compile("^(?:" + s + ")$") if err != nil { return err } re.Regexp = regex return nil } prometheus-snmp-exporter-0.16.1+ds/config_test.go000066400000000000000000000030051360361176200220530ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "strings" "testing" yaml "gopkg.in/yaml.v2" ) func TestHideConfigSecrets(t *testing.T) { sc := &SafeConfig{} err := sc.ReloadConfig("testdata/snmp-auth.yml") if err != nil { t.Errorf("Error loading config %v: %v", "testdata/snmp-auth.yml", err) } // String method must not reveal authentication credentials. sc.RLock() c, err := yaml.Marshal(sc.C) sc.RUnlock() if err != nil { t.Errorf("Error marshaling config: %v", err) } if strings.Contains(string(c), "mysecret") { t.Fatal("config's String method reveals authentication credentials.") } } func TestLoadConfigWithOverrides(t *testing.T) { sc := &SafeConfig{} err := sc.ReloadConfig("testdata/snmp-with-overrides.yml") if err != nil { t.Errorf("Error loading config %v: %v", "testdata/snmp-with-overrides.yml", err) } sc.RLock() _, err = yaml.Marshal(sc.C) sc.RUnlock() if err != nil { t.Errorf("Error marshaling config: %v", err) } } prometheus-snmp-exporter-0.16.1+ds/generator/000077500000000000000000000000001360361176200212105ustar00rootroot00000000000000prometheus-snmp-exporter-0.16.1+ds/generator/Dockerfile000066400000000000000000000005161360361176200232040ustar00rootroot00000000000000FROM golang:latest RUN apt-get update && \ apt-get install -y libsnmp-dev && \ go get github.com/prometheus/snmp_exporter/generator && \ cd /go/src/github.com/prometheus/snmp_exporter/generator && \ go get -v . && \ go install WORKDIR "/opt" ENTRYPOINT ["/go/bin/generator"] ENV MIBDIRS mibs CMD ["generate"] prometheus-snmp-exporter-0.16.1+ds/generator/FORMAT.md000066400000000000000000000046741360361176200225350ustar00rootroot00000000000000# Exporter file format This is generated by the generator, so only those doing development should have to care about how this works. ``` module_name: auth: # There's various auth/version options here too. See the main README. community: public walk: # List of OID subtrees to walk. - 1.3.6.1.2.1.2 - 1.3.6.1.2.1.31.1.1 get: # List of OIDs to get directly. - 1.3.6.1.2.1.1.3 metrics: # List of metrics to extract. # A simple metric with no labels. - name: sysUpTime oid: 1.3.6.1.2.1.1.3 type: gauge # See README.md type override for a list of valid types # Non-numeric types are represented as a gauge with value 1, and the rendered value # as a label value on that gauge. # A metric that's part of a table, and thus has labels. - name: ifMtu oid: 1.3.6.1.2.1.2.2.1.4 type: gauge # A list of the table indexes and their types. All indexes become labels. indexes: - labelname: ifIndex type: gauge - labelname: someString type: OctetString fixed_size: 8 # Only possible for OctetString/DisplayString types. # If only one length is possible this is it. Otherwise # this will be 0 or missing. - labelname: someOtherString type: OctetString implied: true # Only possible for OctetString/DisplayString types. # Must be the last index. See RFC2578 section 7.7. - name: ifSpeed oid: 1.3.6.1.2.1.2.2.1.5 type: gauge indexes: - labelname: ifDescr type: gauge # Lookups take original indexes, look them up in another part of the # oid tree and overwrite the given output label. lookups: - labels: [ifDescr] # Input label name(s). Empty means delete the output label. oid: 1.3.6.1.2.1.2.2.1.2 # OID to look under. labelname: ifDescr # Output label name. type: OctetString # Type of output object. # Creates new metrics based on the regex and the metric value. regex_extracts: Temp: # A new metric will be created appending this to the metricName to become metricNameTemp. - regex: '(.*)' # Regex to extract a value from the returned SNMP walks's value. value: '$1' # Parsed as float64, defaults to $1. enum_values: # Enum for this metric. Only used with the enum types. 0: true 1: false ``` prometheus-snmp-exporter-0.16.1+ds/generator/Makefile000066400000000000000000000212341360361176200226520ustar00rootroot00000000000000# Copyright 2018 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. MIBDIR := mibs MIB_PATH := 'mibs' CURL_OPTS ?= -s --retry 3 --retry-delay 3 --compressed --location DOCKER_IMAGE_NAME ?= snmp-generator DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) DOCKER_REPO ?= prom APC_URL := 'https://download.schneider-electric.com/files?p_File_Name=powernet428.mib' ARISTA_URL := https://www.arista.com/assets/data/docs/MIBS CISCO_URL := 'ftp://ftp.cisco.com/pub/mibs/v2/v2.tar.gz' FRAMEWORK_URL := http://www.net-snmp.org/docs/mibs/SNMP-FRAMEWORK-MIB.txt IANA_CHARSET_URL := https://www.iana.org/assignments/ianacharset-mib/ianacharset-mib IANA_IFTYPE_URL := https://www.iana.org/assignments/ianaiftype-mib/ianaiftype-mib IANA_PRINTER_URL := https://www.iana.org/assignments/ianaprinter-mib/ianaprinter-mib IPV6_TC_URL := http://www.net-snmp.org/docs/mibs/IPV6-TC.txt KEEPALIVED_URL := 'https://raw.githubusercontent.com/acassen/keepalived/master/doc/KEEPALIVED-MIB.txt' MIKROTIK_URL := 'http://download2.mikrotik.com/Mikrotik.mib' NEC_URL := https://jpn.nec.com/univerge/ix/Manual/MIB NET_SNMP_URL := http://www.net-snmp.org/docs/mibs/NET-SNMP-MIB.txt NET_TC_URL := http://www.net-snmp.org/docs/mibs/NET-SNMP-TC.txt PALOALTO_URL := 'https://www.paloaltonetworks.com/content/dam/pan/en_US/assets/zip/technical-documentation/snmp-mib-modules/PAN-MIB-MODULES-7.0.zip' PRINTER_URL := https://ftp.pwg.org/pub/pwg/pmp/mibs/rfc3805b.mib SERVERTECH_URL := 'https://cdn10.servertech.com/assets/documents/documents/817/original/Sentry3.mib' SYNOLOGY_URL := 'https://global.download.synology.com/download/Document/MIBGuide/Synology_MIB_File.zip' UBNT_AIROS_URL := https://dl.ubnt.com/firmwares/airos-ubnt-mib/ubnt-mib.zip UBNT_DL_URL := http://dl.ubnt-ut.com/snmp UCD_URL := http://www.net-snmp.org/docs/mibs/UCD-SNMP-MIB.txt .DEFAULT: all .PHONY: all all: mibs clean: rm -v \ $(MIBDIR)/* \ $(MIBDIR)/cisco_v2 \ $(MIBDIR)/.cisco_v2 \ $(MIBDIR)/.paloalto_panos \ $(MIBDIR)/.synology generator: *.go go build generate: generator mibs MIBDIRS=$(MIB_PATH) ./generator --fail-on-parse-errors generate parse_errors: generator mibs MIBDIRS=$(MIB_PATH) ./generator --fail-on-parse-errors parse_errors .PHONY: docker docker: docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" . .PHONY: docker-publish docker-publish: docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)" .PHONY: docker-tag-latest docker-tag-latest: docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):latest" .PHONY: mibs mibs: mib-dir \ $(MIBDIR)/apc-powernet-mib \ $(MIBDIR)/ARISTA-ENTITY-SENSOR-MIB \ $(MIBDIR)/ARISTA-SMI-MIB \ $(MIBDIR)/ARISTA-SW-IP-FORWARDING-MIB \ $(MIBDIR)/.cisco_v2 \ $(MIBDIR)/IANA-CHARSET-MIB.txt \ $(MIBDIR)/IANA-IFTYPE-MIB.txt \ $(MIBDIR)/IANA-PRINTER-MIB.txt \ $(MIBDIR)/IPV6-TC \ $(MIBDIR)/KEEPALIVED-MIB \ $(MIBDIR)/MIKROTIK-MIB \ $(MIBDIR)/NET-SNMP-MIB \ $(MIBDIR)/NET-SNMP-TC \ $(MIBDIR)/.paloalto_panos \ $(MIBDIR)/PICO-IPSEC-FLOW-MONITOR-MIB.txt \ $(MIBDIR)/PICO-SMI-ID-MIB.txt \ $(MIBDIR)/PICO-SMI-MIB.txt \ $(MIBDIR)/PRINTER-MIB-V2.txt \ $(MIBDIR)/servertech-sentry3-mib \ $(MIBDIR)/SNMP-FRAMEWORK-MIB \ $(MIBDIR)/.synology \ $(MIBDIR)/UBNT-MIB \ $(MIBDIR)/UBNT-UniFi-MIB \ $(MIBDIR)/UBNT-AirMAX-MIB.txt \ $(MIBDIR)/UCD-SNMP-MIB.txt mib-dir: @mkdir -p -v $(MIBDIR) $(MIBDIR)/apc-powernet-mib: @echo ">> Downloading apc-powernet-mib" @curl $(CURL_OPTS) -o $(MIBDIR)/apc-powernet-mib -L $(APC_URL) $(MIBDIR)/ARISTA-ENTITY-SENSOR-MIB: @echo ">> Downloading ARISTA-ENTITY-SENSOR-MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/ARISTA-ENTITY-SENSOR-MIB -L "$(ARISTA_URL)/ARISTA-ENTITY-SENSOR-MIB.txt" $(MIBDIR)/ARISTA-SMI-MIB: @echo ">> Downloading ARISTA-SMI-MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/ARISTA-SMI-MIB -L "$(ARISTA_URL)/ARISTA-SMI-MIB.txt" $(MIBDIR)/ARISTA-SW-IP-FORWARDING-MIB: @echo ">> Downloading ARISTA-SW-IP-FORWARDING-MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/ARISTA-SW-IP-FORWARDING-MIB -L "$(ARISTA_URL)/ARISTA-SW-IP-FORWARDING-MIB.txt" $(MIBDIR)/.cisco_v2: $(eval TMP := $(shell mktemp)) @echo ">> Downloading cisco_v2" @mkdir -p $(MIBDIR)/cisco_v2 @curl $(CURL_OPTS) -o $(TMP) -L $(CISCO_URL) tar --no-same-owner -C $(MIBDIR)/cisco_v2 --strip-components=3 -zxvf $(TMP) cp mibs/cisco_v2/AIRESPACE-REF-MIB.my mibs/AIRESPACE-REF-MIB cp mibs/cisco_v2/AIRESPACE-WIRELESS-MIB.my mibs/AIRESPACE-WIRELESS-MIB cp mibs/cisco_v2/ENTITY-MIB.my mibs/ENTITY-MIB cp mibs/cisco_v2/ENTITY-SENSOR-MIB.my mibs/ENTITY-SENSOR-MIB cp mibs/cisco_v2/ENTITY-STATE-MIB.my mibs/ENTITY-STATE-MIB cp mibs/cisco_v2/ENTITY-STATE-TC-MIB.my mibs/ENTITY-STATE-TC-MIB cp mibs/cisco_v2/HOST-RESOURCES-MIB.my mibs/HOST-RESOURCES-MIB cp mibs/cisco_v2/IF-MIB.my mibs/IF-MIB cp mibs/cisco_v2/INET-ADDRESS-MIB.my mibs/INET-ADDRESS-MIB cp mibs/cisco_v2/ISDN-MIB.my mibs/ISDN-MIB cp mibs/cisco_v2/SNMPv2-MIB.my mibs/SNMPv2-MIB cp mibs/cisco_v2/SNMPv2-SMI.my mibs/SNMPv2-SMI cp mibs/cisco_v2/SNMPv2-TC.my mibs/SNMPv2-TC @rm -v $(TMP) @touch $(MIBDIR)/.cisco_v2 $(MIBDIR)/IANA-CHARSET-MIB.txt: @echo ">> Downloading IANA charset MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/IANA-CHARSET-MIB.txt -L $(IANA_CHARSET_URL) $(MIBDIR)/IANA-IFTYPE-MIB.txt: @echo ">> Downloading IANA ifType MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/IANA-IFTYPE-MIB.txt -L $(IANA_IFTYPE_URL) $(MIBDIR)/IANA-PRINTER-MIB.txt: @echo ">> Downloading IANA printer MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/IANA-PRINTER-MIB.txt -L $(IANA_PRINTER_URL) $(MIBDIR)/IPV6-TC: @echo ">> Downloading IPV6-TC" @curl $(CURL_OPTS) -o $(MIBDIR)/IPV6-TC -L $(IPV6_TC_URL) $(MIBDIR)/KEEPALIVED-MIB: @echo ">> Downloading KEEPALIVED-MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/KEEPALIVED-MIB -L $(KEEPALIVED_URL) $(MIBDIR)/MIKROTIK-MIB: @echo ">> Downloading MIKROTIK-MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/MIKROTIK-MIB -L $(MIKROTIK_URL) $(MIBDIR)/NET-SNMP-MIB: @echo ">> Downloading NET-SNMP-MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/NET-SNMP-MIB -L $(NET_SNMP_URL) $(MIBDIR)/NET-SNMP-TC: @echo ">> Downloading NET-SNMP-TC" @curl $(CURL_OPTS) -o $(MIBDIR)/NET-SNMP-TC -L $(NET_TC_URL) $(MIBDIR)/PICO-IPSEC-FLOW-MONITOR-MIB.txt: @echo ">> Downloading PICO-IPSEC-FLOW-MONITOR-MIB.txt" @curl $(CURL_OPTS) -o $(MIBDIR)/PICO-IPSEC-FLOW-MONITOR-MIB.txt -L "$(NEC_URL)/PICO-IPSEC-FLOW-MONITOR-MIB.txt" $(MIBDIR)/PICO-SMI-MIB.txt: @echo ">> Downloading PICO-SMI-MIB.txt" @curl $(CURL_OPTS) -o $(MIBDIR)/PICO-SMI-MIB.txt -L "$(NEC_URL)/PICO-SMI-MIB.txt" $(MIBDIR)/PICO-SMI-ID-MIB.txt: @echo ">> Downloading PICO-SMI-ID-MIB.txt" @curl $(CURL_OPTS) -o $(MIBDIR)/PICO-SMI-ID-MIB.txt -L "$(NEC_URL)/PICO-SMI-ID-MIB.txt" $(MIBDIR)/.paloalto_panos: $(eval TMP := $(shell mktemp)) @echo ">> Downloading paloalto_pano to $(TMP)" @curl $(CURL_OPTS) -o $(TMP) -L $(PALOALTO_URL) @unzip -j -d $(MIBDIR) $(TMP) @rm -v $(TMP) @touch $(MIBDIR)/.paloalto_panos $(MIBDIR)/PRINTER-MIB-V2.txt: @echo ">> Downloading Printer MIB v2" @curl $(CURL_OPTS) -o $(MIBDIR)/PRINTER-MIB-V2.txt -L $(PRINTER_URL) $(MIBDIR)/servertech-sentry3-mib: @echo ">> Downloading servertech-sentry3-mib" @curl $(CURL_OPTS) -o $(MIBDIR)/servertech-sentry3-mib -L $(SERVERTECH_URL) $(MIBDIR)/SNMP-FRAMEWORK-MIB: @echo ">> Downloading SNMP-FRAMEWORK-MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/SNMP-FRAMEWORK-MIB -L $(FRAMEWORK_URL) $(MIBDIR)/.synology: $(eval TMP := $(shell mktemp)) @echo ">> Downloading synology to $(TMP)" @curl $(CURL_OPTS) -o $(TMP) -L $(SYNOLOGY_URL) @unzip -j -d $(MIBDIR) $(TMP) @rm -v $(TMP) @touch $(MIBDIR)/.synology $(MIBDIR)/UBNT-MIB: @echo ">> Downloading UBNT-MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/UBNT-MIB -L "$(UBNT_DL_URL)/UBNT-MIB" $(MIBDIR)/UBNT-UniFi-MIB: @echo ">> Downloading UBNT-UniFi-MIB" @curl $(CURL_OPTS) -o $(MIBDIR)/UBNT-UniFi-MIB -L "$(UBNT_DL_URL)/UBNT-UniFi-MIB" $(MIBDIR)/UBNT-AirMAX-MIB.txt: $(eval TMP := $(shell mktemp)) @echo ">> Downloading ubnt-airos to $(TMP)" @curl $(CURL_OPTS) -o $(TMP) -L $(UBNT_AIROS_URL) @unzip -j -d $(MIBDIR) $(TMP) UBNT-AirMAX-MIB.txt @rm -v $(TMP) $(MIBDIR)/UCD-SNMP-MIB.txt: @echo ">> Downloading UCD-SNMP-MIB.txt" @curl $(CURL_OPTS) -o $(MIBDIR)/UCD-SNMP-MIB.txt -L "$(UCD_URL)" prometheus-snmp-exporter-0.16.1+ds/generator/README.md000066400000000000000000000214341360361176200224730ustar00rootroot00000000000000 # SNMP Exporter Config Generator This config generator uses NetSNMP to parse MIBs, and generates configs for the snmp_exporter using them. ## Building Due to the dynamic dependency on NetSNMP, you must build the generator yourself. ``` # Debian-based distributions. sudo apt-get install unzip build-essential libsnmp-dev # Debian-based distros # Redhat-based distributions. sudo yum install gcc gcc-g++ make net-snmp net-snmp-utils net-snmp-libs net-snmp-devel # RHEL-based distros go get github.com/prometheus/snmp_exporter/generator cd ${GOPATH-$HOME/go}/src/github.com/prometheus/snmp_exporter/generator go build make mibs ``` ## Running ```sh export MIBDIRS=mibs ./generator generate ``` The generator reads in from `generator.yml` and writes to `snmp.yml`. Additional command are available for debugging, use the `help` command to see them. ## Docker Users If you would like to run the generator in docker to generate your `snmp.yml` config run the following commands. The Docker image expects a directory containing the `generator.yml` and a directory called `mibs` that contains all MIBs you wish to use. This example will generate the example `snmp.yml` which is included in the top level of the snmp_exporter repo: ```sh make mibs docker build -t snmp-generator . docker run -ti \ -v "${PWD}:/opt/" \ snmp-generator generate ``` ## File Format `generator.yml` provides a list of modules. The simplest module is just a name and a set of OIDs to walk. ```yaml modules: module_name: # The module name. You can have as many modules as you want. walk: # List of OIDs to walk. Can also be SNMP object names or specific instances. - 1.3.6.1.2.1.2 # Same as "interfaces" - sysUpTime # Same as "1.3.6.1.2.1.1.3" - 1.3.6.1.2.1.31.1.1.1.6.40 # Instance of "ifHCInOctets" with index "40" version: 2 # SNMP version to use. Defaults to 2. # 1 will use GETNEXT, 2 and 3 use GETBULK. max_repetitions: 25 # How many objects to request with GET/GETBULK, defaults to 25. # May need to be reduced for buggy devices. retries: 3 # How many times to retry a failed request, defaults to 3. timeout: 10s # Timeout for each walk, defaults to 10s. auth: # Community string is used with SNMP v1 and v2. Defaults to "public". community: public # v3 has different and more complex settings. # Which are required depends on the security_level. # The equivalent options on NetSNMP commands like snmpbulkwalk # and snmpget are also listed. See snmpcmd(1). username: user # Required, no default. -u option to NetSNMP. security_level: noAuthNoPriv # Defaults to noAuthNoPriv. -l option to NetSNMP. # Can be noAuthNoPriv, authNoPriv or authPriv. password: pass # Has no default. Also known as authKey, -A option to NetSNMP. # Required if security_level is authNoPriv or authPriv. auth_protocol: MD5 # MD5 or SHA, defaults to MD5. -a option to NetSNMP. # Used if security_level is authNoPriv or authPriv. priv_protocol: DES # DES or AES, defaults to DES. -x option to NetSNMP. # Used if security_level is authPriv. priv_password: otherPass # Has no default. Also known as privKey, -X option to NetSNMP. # Required if security_level is authPriv. context_name: context # Has no default. -n option to NetSNMP. # Required if context is configured on the device. lookups: # Optional list of lookups to perform. # The default for `keep_source_indexes` is false. Indexes must be unique for this option to be used. # If the index of a table is bsnDot11EssIndex, usually that'd be the label # on the resulting metrics from that table. Instead, use the index to # lookup the bsnDot11EssSsid table entry and create a bsnDot11EssSsid label # with that value. - source_indexes: [bsnDot11EssIndex] lookup: bsnDot11EssSsid drop_source_indexes: false # If true, delete source index labels for this lookup. # This avoids label clutter when the new index is unique. overrides: # Allows for per-module overrides of bits of MIBs metricName: ignore: true # Drops the metric from the output. regex_extracts: Temp: # A new metric will be created appending this to the metricName to become metricNameTemp. - regex: '(.*)' # Regex to extract a value from the returned SNMP walks's value. value: '$1' # The result will be parsed as a float64, defaults to $1. Status: - regex: '.*Example' value: '1' - regex: '.*' value: '0' type: DisplayString # Override the metric type, possible types are: # gauge: An integer with type gauge. # counter: An integer with type counter. # OctetString: A bit string, rendered as 0xff34. # DateAndTime: An RFC 2579 DateAndTime byte sequence. If the device has no time zone data, UTC is used. # DisplayString: An ASCII or UTF-8 string. # PhysAddress48: A 48 bit MAC address, rendered as 00:01:02:03:04:ff. # Float: A 32 bit floating-point value with type gauge. # Double: A 64 bit floating-point value with type gauge. # InetAddressIPv4: An IPv4 address, rendered as 1.2.3.4. # InetAddressIPv6: An IPv6 address, rendered as 0102:0304:0506:0708:090A:0B0C:0D0E:0F10. # InetAddress: An InetAddress per RFC 4001. Must be preceded by an InetAddressType. # InetAddressMissingSize: An InetAddress that violates section 4.1 of RFC 4001 by # not having the size in the index. Must be preceded by an InetAddressType. # EnumAsInfo: An enum for which a single timeseries is created. Good for constant values. # EnumAsStateSet: An enum with a time series per state. Good for variable low-cardinality enums. # Bits: An RFC 2578 BITS construct, which produces a StateSet with a time series per bit. ``` ### EnumAsInfo and EnumAsStateSet SNMP contains the concept of integer indexed enumerations (enums). There are two ways to represent these strings in Prometheus. They can be "info" metrics, or they can be "state sets". SNMP does not specify which should be used, and it's up to the use case of the data. Some users may also prefer the raw integer value, rather than the string. In order to set enum integer to string mapping, you must use one of the two overrides. `EnumAsInfo` should be used for properties that provide inventory-like data. For example a device type, the name of a colour etc. It is important that this value is constant. `EnumAsStateSet` should be used for things that represent state or that you might want to alert on. For example the link state, is it up or down, is it in an error state, whether a panel is open or closed etc. Please be careful to not use this for high cardinality values as it will generate 1 time series per possible value. ## Where to get MIBs Some of these are quite sluggish, so use wget to download. Put the extracted mibs in a location NetSNMP can read them from. `$HOME/.snmp/mibs` is one option. * Cisco: ftp://ftp.cisco.com/pub/mibs/v2/v2.tar.gz * APC: https://download.schneider-electric.com/files?p_File_Name=powernet426.mib * Servertech: ftp://ftp.servertech.com/Pub/SNMP/sentry3/Sentry3.mib * Palo Alto PanOS 7.0 enterprise MIBs: https://www.paloaltonetworks.com/content/dam/pan/en_US/assets/zip/technical-documentation/snmp-mib-modules/PAN-MIB-MODULES-7.0.zip * Arista Networks: https://www.arista.com/assets/data/docs/MIBS/ARISTA-ENTITY-SENSOR-MIB.txt https://www.arista.com/assets/data/docs/MIBS/ARISTA-SW-IP-FORWARDING-MIB.txt https://www.arista.com/assets/data/docs/MIBS/ARISTA-SMI-MIB.txt * Synology: https://global.download.synology.com/download/Document/MIBGuide/Synology_MIB_File.zip * MikroTik: http://download2.mikrotik.com/Mikrotik.mib * UCD-SNMP-MIB (Net-SNMP): http://www.net-snmp.org/docs/mibs/UCD-SNMP-MIB.txt * Ubiquiti Networks: http://dl.ubnt-ut.com/snmp/UBNT-MIB http://dl.ubnt-ut.com/snmp/UBNT-UniFi-MIB https://dl.ubnt.com/firmwares/airos-ubnt-mib/ubnt-mib.zip https://github.com/librenms/librenms/tree/master/mibs can also be a good source of MIBs. http://oidref.com is recommended for browsing MIBs. prometheus-snmp-exporter-0.16.1+ds/generator/config.go000066400000000000000000000036131360361176200230070ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "github.com/prometheus/snmp_exporter/config" ) // The generator config. type Config struct { Modules map[string]*ModuleConfig `yaml:"modules"` } type MetricOverrides struct { Ignore bool `yaml:"ignore,omitempty"` RegexpExtracts map[string][]config.RegexpExtract `yaml:"regex_extracts,omitempty"` Type string `yaml:"type,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *MetricOverrides) UnmarshalYAML(unmarshal func(interface{}) error) error { type plain MetricOverrides if err := unmarshal((*plain)(c)); err != nil { return err } // Ensure type for override is valid if one is defined. typ, ok := metricType(c.Type) if c.Type != "" && (!ok || typ != c.Type) { return fmt.Errorf("invalid metric type override '%s'", c.Type) } return nil } type ModuleConfig struct { Walk []string `yaml:"walk"` Lookups []*Lookup `yaml:"lookups"` WalkParams config.WalkParams `yaml:",inline"` Overrides map[string]MetricOverrides `yaml:"overrides"` } type Lookup struct { SourceIndexes []string `yaml:"source_indexes"` Lookup string `yaml:"lookup"` DropSourceIndexes bool `yaml:"drop_source_indexes,omitempty"` } prometheus-snmp-exporter-0.16.1+ds/generator/generator.yml000066400000000000000000000257361360361176200237360ustar00rootroot00000000000000modules: # Default IF-MIB interfaces table with ifIndex. if_mib: walk: [sysUpTime, interfaces, ifXTable] lookups: - source_indexes: [ifIndex] lookup: ifAlias - source_indexes: [ifIndex] lookup: ifDescr - source_indexes: [ifIndex] # Use OID to avoid conflict with Netscaler NS-ROOT-MIB. lookup: 1.3.6.1.2.1.31.1.1.1.1 # ifName overrides: ifAlias: ignore: true # Lookup metric ifDescr: ignore: true # Lookup metric ifName: ignore: true # Lookup metric ifType: type: EnumAsInfo # Cisco Wireless LAN Controller cisco_wlc: walk: - interfaces - ifXTable - 1.3.6.1.4.1.14179.2.1.1.1.38 # bsnDot11EssNumberofMobileStations - 1.3.6.1.4.1.14179.2.2.2.1.2 # bsnAPIfType - 1.3.6.1.4.1.14179.2.2.2.1.4 # bsnAPIfPhyChannelNumber - 1.3.6.1.4.1.14179.2.2.2.1.15 # bsnApIfNoOfUsers - 1.3.6.1.4.1.14179.2.2.6.1 # bsnAPIfDot11CountersTable - 1.3.6.1.4.1.14179.2.2.13.1.3 # bsnAPIfLoadChannelUtilization - 1.3.6.1.4.1.14179.2.2.15.1.21 # bsnAPIfDBNoisePower lookups: - source_indexes: [bsnDot11EssIndex] lookup: bsnDot11EssSsid drop_source_indexes: true - source_indexes: [bsnAPDot3MacAddress] lookup: bsnAPName drop_source_indexes: true overrides: ifType: type: EnumAsInfo # APC/Schneider UPS Network Management Cards # # Note: older management cards only support SNMP v1 (AP9606 and # AP9607, possibly others). Older versions of the firmware may only # support v1 as well. If you only have newer cards you can switch to # version v2c or v3. # # The management cards have relatively slow processors so don't poll # very often and give a generous timeout to prevent spurious # errors. Alternatively you can eliminate the interface polling (OIDs # beginning with 1.3.6.1.2.1) to reduce the time taken for polling. # # MIB: https://download.schneider-electric.com/files?p_File_Name=powernet426.mib # Guide: http://www.apc.com/salestools/ASTE-6Z5QEY/ASTE-6Z5QEY_R0_EN.pdf # Download site: http://www.apc.com/us/en/tools/download/index.cfm apcups: version: 1 walk: - sysUpTime - interfaces - 1.3.6.1.4.1.318.1.1.1.2 # upsBattery - 1.3.6.1.4.1.318.1.1.1.3 # upsInput - 1.3.6.1.4.1.318.1.1.1.4 # upsOutput - 1.3.6.1.4.1.318.1.1.1.7.2 # upsAdvTest - 1.3.6.1.4.1.318.1.1.1.8.1 # upsCommStatus - 1.3.6.1.4.1.318.1.1.1.12 # upsOutletGroups - 1.3.6.1.4.1.318.1.1.10.2.3.2 # iemStatusProbesTable - 1.3.6.1.4.1.318.1.1.26.8.3 # rPDU2BankStatusTable lookups: - source_indexes: [upsOutletGroupStatusIndex] lookup: upsOutletGroupStatusName drop_source_indexes: true - source_indexes: [iemStatusProbeIndex] lookup: iemStatusProbeName drop_source_indexes: true overrides: ifType: type: EnumAsInfo rPDU2BankStatusLoadState: type: EnumAsStateSet upsAdvBatteryCondition: type: EnumAsStateSet upsAdvBatteryChargingCurrentRestricted: type: EnumAsStateSet upsAdvBatteryChargerStatus: type: EnumAsStateSet # ServerTech Sentry 3 MIB # # Used by ServerTech PDUs # # ftp://ftp.servertech.com/Pub/SNMP/sentry3/Sentry3OIDTree.txt # ftp://ftp.servertech.com/Pub/SNMP/sentry3/Sentry3.mib servertech_sentry3: walk: - sysUpTime - 1.3.6.1.4.1.1718.3.2.2 # infeedTable - 1.3.6.1.4.1.1718.3.2.3 # outletTable overrides: infeedCapacityUsed: ignore: true # Composite metric: infeedLoadValue / infeedCapacity * 100 infeedVACapacityUsed: ignore: true # Composite metric: infeedApparentPower / infeedVACapacity * 100 # Palo Alto Firewalls # # Palo Alto MIBs can be found here: # https://www.paloaltonetworks.com/documentation/misc/snmp-mibs.html # # PanOS 7.0 enterprise MIBs: # https://www.paloaltonetworks.com/content/dam/pan/en_US/assets/zip/technical-documentation/snmp-mib-modules/PAN-MIB-MODULES-7.0.zip # # Tested on a Palo Alto Networks PA-3020 series firewall # paloalto_fw: walk: - sysUpTime - interfaces - hrDevice - hrSystem - hrStorage - 1.3.6.1.4.1.25461.2.1.2.1 # panSys - 1.3.6.1.4.1.25461.2.1.2.3 # panSession - 1.3.6.1.4.1.25461.2.1.2.5 # panGlobalProtect # Arista Networks # # Arista Networks MIBs can be found here: https://www.arista.com/en/support/product-documentation/arista-snmp-mibs # # https://www.arista.com/assets/data/docs/MIBS/ARISTA-ENTITY-SENSOR-MIB.txt # https://www.arista.com/assets/data/docs/MIBS/ARISTA-SW-IP-FORWARDING-MIB.txt # # Tested on Arista DCS-7010T-48 switch # arista_sw: walk: - sysUpTime - interfaces - ifXTable - 1.3.6.1.2.1.25.3.3.1.2 # hrProcessorLoad - 1.3.6.1.2.1.25.2.3.1.6 # hrStorageUsed - 1.3.6.1.4.1.30065.3.1.1 # aristaSwFwdIp overrides: ifType: type: EnumAsInfo # Synology # # Synology MIBs can be found here: # http://www.synology.com/support/snmp_mib.php # http://dedl.synology.com/download/Document/MIBGuide/Synology_MIB_File.zip # # Tested on RS2414rp+ NAS # synology: walk: - interfaces - sysUpTime - ifXTable - laNames - laLoadInt - ssCpuUser - ssCpuSystem - ssCpuIdle - memory - hrStorage - 1.3.6.1.4.1.6574.1 # synoSystem - 1.3.6.1.4.1.6574.2 # synoDisk - 1.3.6.1.4.1.6574.3 # synoRaid - 1.3.6.1.4.1.6574.4 # synoUPS - 1.3.6.1.4.1.6574.5 # synologyDiskSMART - 1.3.6.1.4.1.6574.6 # synologyService - 1.3.6.1.4.1.6574.101 # storageIO - 1.3.6.1.4.1.6574.102 # spaceIO - 1.3.6.1.4.1.6574.104 # synologyiSCSILUN lookups: - source_indexes: [spaceIOIndex] lookup: spaceIODevice drop_source_indexes: true - source_indexes: [storageIOIndex] lookup: storageIODevice drop_source_indexes: true - source_indexes: [serviceInfoIndex] lookup: serviceName drop_source_indexes: true - source_indexes: [ifIndex] # Use OID to avoid conflict with Netscaler NS-ROOT-MIB. lookup: 1.3.6.1.2.1.31.1.1.1.1 # ifName drop_source_indexes: true - source_indexes: [diskIndex] lookup: diskID drop_source_indexes: true - source_indexes: [raidIndex] lookup: raidName drop_source_indexes: true - source_indexes: [laIndex] lookup: laNames drop_source_indexes: true - source_indexes: [hrStorageIndex] lookup: hrStorageDescr drop_source_indexes: true overrides: ifType: type: EnumAsInfo # DD-WRT # # The list of SNMP OIDs to care about for DD-WRT can be found here: https://www.dd-wrt.com/wiki/index.php/SNMP#Known_OID.C2.B4s_via_SNMP # # Tested on DD-WRT v3.0-r31825 (04/06/17) with an ASUS RT-AC66U # ddwrt: walk: - sysUpTime - interfaces - ifXTable - 1.3.6.1.2.1.25.2 # hrStorage - 1.3.6.1.4.1.2021.4 # memory - 1.3.6.1.4.1.2021.10.1.1 # laIndex - 1.3.6.1.4.1.2021.10.1.2 # laNames - 1.3.6.1.4.1.2021.10.1.5 # laLoadInt - 1.3.6.1.4.1.2021.11 # systemStats lookups: - source_indexes: [ifIndex] lookup: ifDescr drop_source_indexes: true - source_indexes: [laIndex] lookup: laNames drop_source_indexes: true - source_indexes: [hrStorageIndex] lookup: hrStorageDescr drop_source_indexes: true overrides: ifType: type: EnumAsInfo # Ubiquiti / AirMAX # # https://dl.ubnt.com/firmwares/airos-ubnt-mib/ubnt-mib.zip # ubiquiti_airmax: version: 1 walk: - sysUpTime - interfaces - ifXTable - 1.3.6.1.4.1.41112.1.4 # ubntAirMAX overrides: ifType: type: EnumAsInfo # Ubiquiti / UniFi # # http://dl.ubnt-ut.com/snmp/UBNT-MIB # http://dl.ubnt-ut.com/snmp/UBNT-UniFi-MIB # ubiquiti_unifi: walk: - sysUpTime - interfaces - ifXTable - 1.3.6.1.4.1.41112.1.6 # ubntUniFi overrides: ifType: type: EnumAsInfo # keepalived # # https://github.com/acassen/keepalived/blob/master/doc/KEEPALIVED-MIB.txt keepalived: walk: - vrrpInstanceTable # Table of VRRP instances. - vrrpSyncGroupTable # Table of sync groups. - virtualServerGroupTable # Table of virtual server groups. - virtualServerTable # Table of virtual servers. - realServerTable # Table of real servers. This includes regular real servers and sorry servers. overrides: vrrpSyncGroupScriptMaster: ignore: true # Non-metric display string. vrrpSyncGroupScriptBackup: ignore: true # Non-metric display string. vrrpSyncGroupScriptFault: ignore: true # Non-metric display string. vrrpSyncGroupScript: ignore: true # Non-metric display string. vrrpSyncGroupScriptStop: ignore: true # Non-metric display string. vrrpInstanceLvsSyncDaemon: ignore: true # Deprecated. vrrpInstanceLvsSyncInterface: ignore: true # Deprecated. vrrpInstanceScriptMaster: ignore: true # Non-metric display string. vrrpInstanceScriptBackup: ignore: true # Non-metric display string. vrrpInstanceScriptFault: ignore: true # Non-metric display string. vrrpInstanceScriptStop: ignore: true # Non-metric display string. vrrpInstanceScript: ignore: true # Non-metric display string. vrrpInstanceScriptMstrRxLowerPri: ignore: true # Non-metric display string. # Printer: RFC 3805 # # https://tools.ietf.org/html/rfc3805 # https://www.iana.org/assignments/ianaprinter-mib/ianaprinter-mib.xhtml printer_mib: walk: - sysUpTime - hrPrinterStatus - prtGeneralReset - prtConsoleDisable - prtGeneralPrinterName - prtGeneralSerialNumber - prtAlertCriticalEvents - prtAlertAllEvents - prtCoverStatus - prtMarkerSuppliesDescription - prtMarkerSuppliesLevel - prtMarkerSuppliesMaxCapacity - prtMarkerSuppliesType lookups: - source_indexes: [hrDeviceIndex, prtMarkerSuppliesIndex] lookup: prtMarkerSuppliesType overrides: hrPrinterStatus: type: EnumAsStateSet prtGeneralReset: type: EnumAsStateSet prtConsoleDisable: type: EnumAsStateSet prtGeneralPrinterName: type: DisplayString prtGeneralSerialNumber: type: DisplayString prtCoverStatus: type: EnumAsStateSet prtMarkerSuppliesDescription: type: DisplayString # NEC IX Router # # https://jpn.nec.com/univerge/ix/Manual/MIB/PICO-SMI-MIB.txt # https://jpn.nec.com/univerge/ix/Manual/MIB/PICO-SMI-ID-MIB.txt # https://jpn.nec.com/univerge/ix/Manual/MIB/PICO-IPSEC-FLOW-MONITOR-MIB.txt nec_ix: walk: - picoSystem - picoIpSecFlowMonitorMIB - picoExtIfMIB - picoNetworkMonitorMIB - picoIsdnMIB - picoNgnMIB - picoMobileMIB - picoIPv4MIB - picoIPv6MIB prometheus-snmp-exporter-0.16.1+ds/generator/main.go000066400000000000000000000115001360361176200224600ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "io/ioutil" "os" "path/filepath" "strings" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/common/promlog" "github.com/prometheus/common/promlog/flag" "gopkg.in/alecthomas/kingpin.v2" "gopkg.in/yaml.v2" "github.com/prometheus/snmp_exporter/config" ) // Generate a snmp_exporter config and write it out. func generateConfig(nodes *Node, nameToNode map[string]*Node, logger log.Logger) error { outputPath, err := filepath.Abs(*outputPath) if err != nil { return fmt.Errorf("unable to determine absolute path for output") } content, err := ioutil.ReadFile("generator.yml") if err != nil { return fmt.Errorf("error reading yml config: %s", err) } cfg := &Config{} err = yaml.UnmarshalStrict(content, cfg) if err != nil { return fmt.Errorf("error parsing yml config: %s", err) } outputConfig := config.Config{} for name, m := range cfg.Modules { level.Info(logger).Log("msg", "Generating config for module", "module", name) // Give each module a copy of the tree so that it can be modified. mNodes := nodes.Copy() // Build the map with new pointers. mNameToNode := map[string]*Node{} walkNode(mNodes, func(n *Node) { mNameToNode[n.Oid] = n mNameToNode[n.Label] = n }) out, err := generateConfigModule(m, mNodes, mNameToNode, logger) if err != nil { return err } outputConfig[name] = out outputConfig[name].WalkParams = m.WalkParams level.Info(logger).Log("msg", "Generated metrics", "module", name, "metrics", len(outputConfig[name].Metrics)) } config.DoNotHideSecrets = true out, err := yaml.Marshal(outputConfig) config.DoNotHideSecrets = false if err != nil { return fmt.Errorf("error marshaling yml: %s", err) } // Check the generated config to catch auth/version issues. err = yaml.UnmarshalStrict(out, &config.Config{}) if err != nil { return fmt.Errorf("error parsing generated config: %s", err) } f, err := os.Create(outputPath) if err != nil { return fmt.Errorf("error opening output file: %s", err) } out = append([]byte("# WARNING: This file was auto-generated using snmp_exporter generator, manual changes will be lost.\n"), out...) _, err = f.Write(out) if err != nil { return fmt.Errorf("error writing to output file: %s", err) } level.Info(logger).Log("msg", "Config written", "file", outputPath) return nil } var ( failOnParseErrors = kingpin.Flag("fail-on-parse-errors", "Exit with a non-zero status if there are MIB parsing errors").Default("false").Bool() generateCommand = kingpin.Command("generate", "Generate snmp.yml from generator.yml") outputPath = generateCommand.Flag("output-path", "Path to to write resulting config file").Default("snmp.yml").Short('o').String() parseErrorsCommand = kingpin.Command("parse_errors", "Debug: Print the parse errors output by NetSNMP") dumpCommand = kingpin.Command("dump", "Debug: Dump the parsed and prepared MIBs") ) func main() { promlogConfig := &promlog.Config{} flag.AddFlags(kingpin.CommandLine, promlogConfig) kingpin.HelpFlag.Short('h') command := kingpin.Parse() logger := promlog.New(promlogConfig) parseOutput, err := initSNMP(logger) if err != nil { level.Error(logger).Log("msg", "Error initializing netsnmp", "err", err) os.Exit(1) } parseOutput = strings.TrimSpace(parseOutput) parseErrors := len(parseOutput) != 0 if parseErrors { level.Warn(logger).Log("msg", "NetSNMP reported parse error(s)", "errors", len(strings.Split(parseOutput, "\n"))) } nodes := getMIBTree() nameToNode := prepareTree(nodes, logger) switch command { case generateCommand.FullCommand(): err := generateConfig(nodes, nameToNode, logger) if err != nil { level.Error(logger).Log("msg", "Error generating config netsnmp", "err", err) os.Exit(1) } case parseErrorsCommand.FullCommand(): fmt.Println(parseOutput) case dumpCommand.FullCommand(): walkNode(nodes, func(n *Node) { t := n.Type if n.FixedSize != 0 { t = fmt.Sprintf("%s(%d)", n.Type, n.FixedSize) } implied := "" if n.ImpliedIndex { implied = "(implied)" } fmt.Printf("%s %s %s %q %q %s%s %v %s\n", n.Oid, n.Label, t, n.TextualConvention, n.Hint, n.Indexes, implied, n.EnumValues, n.Description) }) } if *failOnParseErrors && parseErrors { os.Exit(1) } } prometheus-snmp-exporter-0.16.1+ds/generator/net_snmp.go000066400000000000000000000136611360361176200233710ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main /* #cgo LDFLAGS: -lnetsnmp -L/usr/local/lib #cgo CFLAGS: -I/usr/local/include #include #include #include // From parse.c #define MAXTC 4096 struct tc { int type; int modid; char *descriptor; char *hint; struct enum_list *enums; struct range_list *ranges; char *description; } tclist[MAXTC]; // Return the size of a fixed, or 0 if it is not fixed. int get_tc_fixed_size(int tc_index) { if (tc_index < 0 || tc_index >= MAXTC) { return 0; } struct range_list *ranges; ranges = tclist[tc_index].ranges; // Look for one range with only one possible value. if (ranges == NULL || ranges->low != ranges->high || ranges->next != NULL) { return 0; } return ranges->low; } */ import "C" import ( "fmt" "io/ioutil" "os" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" ) // One entry in the tree of the MIB. type Node struct { Oid string Label string Augments string Children []*Node Description string Type string Hint string TextualConvention string FixedSize int Units string Access string EnumValues map[int]string Indexes []string ImpliedIndex bool } // Copy returns a deep copy of the tree underneath the current Node. func (n *Node) Copy() *Node { newNode := *n newNode.Children = make([]*Node, 0, len(n.Children)) newNode.EnumValues = make(map[int]string, len(n.EnumValues)) newNode.Indexes = make([]string, len(n.Indexes)) copy(newNode.Indexes, n.Indexes) // Deep copy children and enums. for _, child := range n.Children { newNode.Children = append(newNode.Children, child.Copy()) } for k, v := range n.EnumValues { newNode.EnumValues[k] = v } return &newNode } // Adapted from parse.h. var ( netSnmptypeMap = map[int]string{ 0: "OTHER", 1: "OBJID", 2: "OCTETSTR", 3: "INTEGER", 4: "NETADDR", 5: "IPADDR", 6: "COUNTER", 7: "GAUGE", 8: "TIMETICKS", 9: "OPAQUE", 10: "NULL", 11: "COUNTER64", 12: "BITSTRING", 13: "NSAPADDRESS", 14: "UINTEGER", 15: "UNSIGNED32", 16: "INTEGER32", 20: "TRAPTYPE", 21: "NOTIFTYPE", 22: "OBJGROUP", 23: "NOTIFGROUP", 24: "MODID", 25: "AGENTCAP", 26: "MODCOMP", 27: "OBJIDENTITY", } netSnmpaccessMap = map[int]string{ 18: "ACCESS_READONLY", 19: "ACCESS_READWRITE", 20: "ACCESS_WRITEONLY", 21: "ACCESS_NOACCESS", 67: "ACCESS_NOTIFY", 48: "ACCESS_CREATE", } ) // Initialize NetSNMP. Returns MIB parse errors. // // Warning: This function plays with the stderr file descriptor. func initSNMP(logger log.Logger) (string, error) { // Load all the MIBs. os.Setenv("MIBS", "ALL") // Help the user find their MIB directories. level.Info(logger).Log("msg", "Loading MIBs", "from", C.GoString(C.netsnmp_get_mib_directory())) // We want the descriptions. C.snmp_set_save_descriptions(1) // Make stderr go to a pipe, as netsnmp tends to spew a // lot of errors on startup that there's no apparent // way to disable or redirect. r, w, err := os.Pipe() if err != nil { return "", fmt.Errorf("error creating pipe: %s", err) } defer r.Close() defer w.Close() savedStderrFd := C.dup(2) C.close(2) C.dup2(C.int(w.Fd()), 2) ch := make(chan string) errch := make(chan error) go func() { data, err := ioutil.ReadAll(r) if err != nil { errch <- fmt.Errorf("error reading from pipe: %s", err) return } errch <- nil ch <- string(data) }() // Do the initialization. C.netsnmp_init_mib() // Restore stderr to normal. w.Close() C.close(2) C.dup2(savedStderrFd, 2) C.close(savedStderrFd) if err := <-errch; err != nil { return "", err } return <-ch, nil } // Walk NetSNMP MIB tree, building a Go tree from it. func buildMIBTree(t *C.struct_tree, n *Node, oid string) { if oid != "" { n.Oid = fmt.Sprintf("%s.%d", oid, t.subid) } else { n.Oid = fmt.Sprintf("%d", t.subid) } n.Label = C.GoString(t.label) if typ, ok := netSnmptypeMap[int(t._type)]; ok { n.Type = typ } else { n.Type = "unknown" } if access, ok := netSnmpaccessMap[int(t.access)]; ok { n.Access = access } else { n.Access = "unknown" } n.Augments = C.GoString(t.augments) n.Description = C.GoString(t.description) n.Hint = C.GoString(t.hint) n.TextualConvention = C.GoString(C.get_tc_descriptor(t.tc_index)) n.FixedSize = int(C.get_tc_fixed_size(t.tc_index)) n.Units = C.GoString(t.units) n.EnumValues = map[int]string{} enum := t.enums for enum != nil { n.EnumValues[int(enum.value)] = C.GoString(enum.label) enum = enum.next } if t.child_list == nil { return } head := t.child_list n.Children = []*Node{} for head != nil { child := &Node{} // Prepend, as nodes are backwards. n.Children = append([]*Node{child}, n.Children...) buildMIBTree(head, child, n.Oid) head = head.next_peer } // Set names of indexes on each child. // In practice this means only the entry will have it. index := t.indexes indexes := []string{} for index != nil { indexes = append(indexes, C.GoString(index.ilabel)) if index.isimplied != 0 { n.ImpliedIndex = true } index = index.next } n.Indexes = indexes } // Convert the NetSNMP MIB tree to a Go data structure. func getMIBTree() *Node { tree := C.get_tree_head() head := &Node{} buildMIBTree(tree, head, "") return head } prometheus-snmp-exporter-0.16.1+ds/generator/tree.go000066400000000000000000000326271360361176200225100ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "regexp" "sort" "strconv" "strings" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/snmp_exporter/config" ) // These types have one following the other. // We need to check indexes and sequences have them // in the right order, so the exporter can handle them. var combinedTypes = map[string]string{ "InetAddress": "InetAddressType", "InetAddressMissingSize": "InetAddressType", "LldpPortId": "LldpPortIdSubtype", } // Helper to walk MIB nodes. func walkNode(n *Node, f func(n *Node)) { f(n) for _, c := range n.Children { walkNode(c, f) } } // Transform the tree. func prepareTree(nodes *Node, logger log.Logger) map[string]*Node { // Build a map from names and oids to nodes. nameToNode := map[string]*Node{} walkNode(nodes, func(n *Node) { nameToNode[n.Oid] = n nameToNode[n.Label] = n }) // Trim down description to first sentence, removing extra whitespace. walkNode(nodes, func(n *Node) { s := strings.Join(strings.Fields(n.Description), " ") n.Description = strings.Split(s, ". ")[0] }) // Fix indexes to "INTEGER" rather than an object name. // Example: snSlotsEntry in LANOPTICS-HUB-MIB. walkNode(nodes, func(n *Node) { indexes := []string{} for _, i := range n.Indexes { if i == "INTEGER" { // Use the TableEntry name. indexes = append(indexes, n.Label) } else { indexes = append(indexes, i) } } n.Indexes = indexes }) // Copy over indexes based on augments. walkNode(nodes, func(n *Node) { if n.Augments == "" { return } augmented, ok := nameToNode[n.Augments] if !ok { level.Warn(logger).Log("msg", "Can't find augmenting node", "augments", n.Augments, "node", n.Label) return } for _, c := range n.Children { c.Indexes = augmented.Indexes c.ImpliedIndex = augmented.ImpliedIndex } n.Indexes = augmented.Indexes n.ImpliedIndex = augmented.ImpliedIndex }) // Copy indexes from table entries down to the entries. walkNode(nodes, func(n *Node) { if len(n.Indexes) != 0 { for _, c := range n.Children { c.Indexes = n.Indexes c.ImpliedIndex = n.ImpliedIndex } } }) // Include both ASCII and UTF-8 in DisplayString, even though DisplayString // is technically only ASCII. displayStringRe := regexp.MustCompile(`^\d+[at]$`) // Apply various tweaks to the types. walkNode(nodes, func(n *Node) { // Set type on MAC addresses and strings. // RFC 2579 switch n.Hint { case "1x:": n.Type = "PhysAddress48" } if displayStringRe.MatchString(n.Hint) { n.Type = "DisplayString" } // Some MIBs refer to RFC1213 for this, which is too // old to have the right hint set. if n.TextualConvention == "DisplayString" { n.Type = "DisplayString" } if n.TextualConvention == "PhysAddress" { n.Type = "PhysAddress48" } // Promote Opaque Float/Double textual convention to type. if n.TextualConvention == "Float" || n.TextualConvention == "Double" { n.Type = n.TextualConvention } // Convert RFC 2579 DateAndTime textual convention to type. if n.TextualConvention == "DateAndTime" { n.Type = "DateAndTime" } // Convert RFC 4001 InetAddress types textual convention to type. if n.TextualConvention == "InetAddressIPv4" || n.TextualConvention == "InetAddressIPv6" || n.TextualConvention == "InetAddress" { n.Type = n.TextualConvention } // Convert LLDP-MIB LldpPortId type textual convention to type. if n.TextualConvention == "LldpPortId" { n.Type = n.TextualConvention } }) return nameToNode } func metricType(t string) (string, bool) { if _, ok := combinedTypes[t]; ok { return t, true } switch t { case "gauge", "INTEGER", "GAUGE", "TIMETICKS", "UINTEGER", "UNSIGNED32", "INTEGER32": return "gauge", true case "counter", "COUNTER", "COUNTER64": return "counter", true case "OctetString", "OCTETSTR": return "OctetString", true case "BITSTRING": return "Bits", true case "InetAddressIPv4", "IpAddr", "IPADDR", "NETADDR": return "InetAddressIPv4", true case "PhysAddress48", "DisplayString", "Float", "Double", "InetAddressIPv6": return t, true case "DateAndTime": return t, true case "EnumAsInfo", "EnumAsStateSet": return t, true default: // Unsupported type. return "", false } } func metricAccess(a string) bool { switch a { case "ACCESS_READONLY", "ACCESS_READWRITE", "ACCESS_CREATE", "ACCESS_NOACCESS": return true default: // the others are inaccessible metrics. return false } } // Reduce a set of overlapping OID subtrees. func minimizeOids(oids []string) []string { sort.Strings(oids) prevOid := "" minimized := []string{} for _, oid := range oids { if !strings.HasPrefix(oid+".", prevOid) || prevOid == "" { minimized = append(minimized, oid) prevOid = oid + "." } } return minimized } // Search node tree for the longest OID match. func searchNodeTree(oid string, node *Node) *Node { if node == nil || !strings.HasPrefix(oid+".", node.Oid+".") { return nil } for _, child := range node.Children { match := searchNodeTree(oid, child) if match != nil { return match } } return node } type oidMetricType uint8 const ( oidNotFound oidMetricType = iota oidScalar oidInstance oidSubtree ) // Find node in SNMP MIB tree that represents the metric. func getMetricNode(oid string, node *Node, nameToNode map[string]*Node) (*Node, oidMetricType) { // Check if is a known OID/name. n, ok := nameToNode[oid] if ok { // Known node, check if OID is a valid metric or a subtree. _, ok = metricType(n.Type) if ok && metricAccess(n.Access) && len(n.Indexes) == 0 { return n, oidScalar } else { return n, oidSubtree } } // Unknown OID/name, search Node tree for longest match. n = searchNodeTree(oid, node) if n == nil { return nil, oidNotFound } // Table instances must be a valid metric node and have an index. _, ok = metricType(n.Type) ok = ok && metricAccess(n.Access) if !ok || len(n.Indexes) == 0 { return nil, oidNotFound } return n, oidInstance } func generateConfigModule(cfg *ModuleConfig, node *Node, nameToNode map[string]*Node, logger log.Logger) (*config.Module, error) { out := &config.Module{} needToWalk := map[string]struct{}{} tableInstances := map[string][]string{} // Apply type overrides for the current module. for name, params := range cfg.Overrides { if params.Type == "" { continue } // Find node to override. n, ok := nameToNode[name] if !ok { level.Warn(logger).Log("msg", "Could not find node to override type", "node", name) continue } // params.Type validated at generator configuration. n.Type = params.Type } // Remove redundant OIDs to be walked. toWalk := []string{} for _, oid := range cfg.Walk { // Resolve name to OID if possible. n, ok := nameToNode[oid] if ok { toWalk = append(toWalk, n.Oid) } else { toWalk = append(toWalk, oid) } } toWalk = minimizeOids(toWalk) // Find all top-level nodes. metricNodes := map[*Node]struct{}{} for _, oid := range toWalk { metricNode, oidType := getMetricNode(oid, node, nameToNode) switch oidType { case oidNotFound: return nil, fmt.Errorf("cannot find oid '%s' to walk", oid) case oidSubtree: needToWalk[oid] = struct{}{} case oidInstance: // Add a trailing period to the OID to indicate a "Get" instead of a "Walk". needToWalk[oid+"."] = struct{}{} // Save instance index for lookup. index := strings.Replace(oid, metricNode.Oid, "", 1) tableInstances[metricNode.Oid] = append(tableInstances[metricNode.Oid], index) case oidScalar: // Scalar OIDs must be accessed using index 0. needToWalk[oid+".0."] = struct{}{} } metricNodes[metricNode] = struct{}{} } // Sort the metrics by OID to make the output deterministic. metrics := make([]*Node, 0, len(metricNodes)) for key := range metricNodes { metrics = append(metrics, key) } sort.Slice(metrics, func(i, j int) bool { return metrics[i].Oid < metrics[j].Oid }) // Find all the usable metrics. for _, metricNode := range metrics { walkNode(metricNode, func(n *Node) { t, ok := metricType(n.Type) if !ok { return // Unsupported type. } if !metricAccess(n.Access) { return // Inaccessible metrics. } metric := &config.Metric{ Name: sanitizeLabelName(n.Label), Oid: n.Oid, Type: t, Help: n.Description + " - " + n.Oid, Indexes: []*config.Index{}, Lookups: []*config.Lookup{}, EnumValues: n.EnumValues, } if cfg.Overrides[metric.Name].Ignore { return // Ignored metric. } prevType := "" for count, i := range n.Indexes { index := &config.Index{Labelname: i} indexNode, ok := nameToNode[i] if !ok { level.Warn(logger).Log("msg", "Could not find index for node", "node", n.Label, "index", i) return } index.Type, ok = metricType(indexNode.Type) if !ok { level.Warn(logger).Log("msg", "Can't handle index type on node", "node", n.Label, "index", i, "type", indexNode.Type) return } index.FixedSize = indexNode.FixedSize if n.ImpliedIndex && count+1 == len(n.Indexes) { index.Implied = true } // Convert (InetAddressType,InetAddress) to (InetAddress) if subtype, ok := combinedTypes[index.Type]; ok { if prevType == subtype { metric.Indexes = metric.Indexes[:len(metric.Indexes)-1] } else { level.Warn(logger).Log("msg", "Can't handle index type on node, missing preceding", "node", n.Label, "type", index.Type, "missing", subtype) return } } prevType = indexNode.TextualConvention metric.Indexes = append(metric.Indexes, index) } out.Metrics = append(out.Metrics, metric) }) } // Apply lookups. for _, metric := range out.Metrics { toDelete := []string{} for _, lookup := range cfg.Lookups { foundIndexes := 0 // See if all lookup indexes are present. for _, index := range metric.Indexes { for _, lookupIndex := range lookup.SourceIndexes { if index.Labelname == lookupIndex { foundIndexes++ } } } if foundIndexes == len(lookup.SourceIndexes) { if _, ok := nameToNode[lookup.Lookup]; !ok { return nil, fmt.Errorf("unknown index '%s'", lookup.Lookup) } indexNode := nameToNode[lookup.Lookup] typ, ok := metricType(indexNode.Type) if !ok { return nil, fmt.Errorf("unknown index type %s for %s", indexNode.Type, lookup.Lookup) } l := &config.Lookup{ Labelname: sanitizeLabelName(indexNode.Label), Type: typ, Oid: indexNode.Oid, } for _, oldIndex := range lookup.SourceIndexes { l.Labels = append(l.Labels, sanitizeLabelName(oldIndex)) } metric.Lookups = append(metric.Lookups, l) // Make sure we walk the lookup OID(s). if len(tableInstances[metric.Oid]) > 0 { for _, index := range tableInstances[metric.Oid] { needToWalk[indexNode.Oid+index+"."] = struct{}{} } } else { needToWalk[indexNode.Oid] = struct{}{} } if lookup.DropSourceIndexes { // Avoid leaving the old labelname around. toDelete = append(toDelete, lookup.SourceIndexes...) } } } for _, l := range toDelete { metric.Lookups = append(metric.Lookups, &config.Lookup{ Labelname: sanitizeLabelName(l), }) } } // Ensure index label names are sane. for _, metric := range out.Metrics { for _, index := range metric.Indexes { index.Labelname = sanitizeLabelName(index.Labelname) } } // Check that the object before an InetAddress is an InetAddressType, // if not, change it to an OctetString. for _, metric := range out.Metrics { if metric.Type == "InetAddress" || metric.Type == "InetAddressMissingSize" { // Get previous oid. oids := strings.Split(metric.Oid, ".") i, _ := strconv.Atoi(oids[len(oids)-1]) oids[len(oids)-1] = strconv.Itoa(i - 1) prevOid := strings.Join(oids, ".") if prevObj, ok := nameToNode[prevOid]; !ok || prevObj.TextualConvention != "InetAddressType" { metric.Type = "OctetString" } else { // Make sure the InetAddressType is included. if len(tableInstances[metric.Oid]) > 0 { for _, index := range tableInstances[metric.Oid] { needToWalk[prevOid+index+"."] = struct{}{} } } else { needToWalk[prevOid] = struct{}{} } } } } // Apply module config overrides to their corresponding metrics. for name, params := range cfg.Overrides { for _, metric := range out.Metrics { if name == metric.Name || name == metric.Oid { metric.RegexpExtracts = params.RegexpExtracts } } } oids := []string{} for k := range needToWalk { oids = append(oids, k) } // Remove redundant OIDs and separate Walk and Get OIDs. for _, k := range minimizeOids(oids) { if k[len(k)-1:] == "." { out.Get = append(out.Get, k[:len(k)-1]) } else { out.Walk = append(out.Walk, k) } } return out, nil } var ( invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`) ) func sanitizeLabelName(name string) string { return invalidLabelCharRE.ReplaceAllString(name, "_") } prometheus-snmp-exporter-0.16.1+ds/generator/tree_test.go000066400000000000000000001524551360361176200235510ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "reflect" "regexp" "testing" "github.com/go-kit/kit/log" "github.com/prometheus/snmp_exporter/config" yaml "gopkg.in/yaml.v2" ) func TestTreePrepare(t *testing.T) { cases := []struct { in *Node out *Node }{ // Descriptions trimmed. { in: &Node{Oid: "1", Description: "A long sentence. Even more detail!"}, out: &Node{Oid: "1", Description: "A long sentence"}, }, // Indexes copied down. { in: &Node{Oid: "1", Label: "labelEntry", Indexes: []string{"myIndex"}, Children: []*Node{ {Oid: "1.1", Label: "labelA"}}, }, out: &Node{Oid: "1", Label: "labelEntry", Indexes: []string{"myIndex"}, Children: []*Node{ {Oid: "1.1", Label: "labelA", Indexes: []string{"myIndex"}}}, }, }, // Augemnts copied over. { in: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableDesc"}, Children: []*Node{ {Oid: "1.1.1.1", Label: "tableDesc"}}}}}, {Oid: "1.2", Label: "augmentingTable", Children: []*Node{ {Oid: "1.2.1", Label: "augmentingTableEntry", Augments: "tableEntry", Children: []*Node{ {Oid: "1.2.1.1", Label: "augmentingA"}}}}}, }, }, out: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableDesc"}, Children: []*Node{ {Oid: "1.1.1.1", Label: "tableDesc", Indexes: []string{"tableDesc"}}}}}}, {Oid: "1.2", Label: "augmentingTable", Children: []*Node{ {Oid: "1.2.1", Label: "augmentingTableEntry", Augments: "tableEntry", Indexes: []string{"tableDesc"}, Children: []*Node{ {Oid: "1.2.1.1", Label: "augmentingA", Indexes: []string{"tableDesc"}}}}}}, }, }, }, // INTEGER indexes fixed. { in: &Node{Oid: "1", Label: "snSlotsEntry", Indexes: []string{"INTEGER"}, Children: []*Node{ {Oid: "1.1", Label: "snSlotsA"}}, }, out: &Node{Oid: "1", Label: "snSlotsEntry", Indexes: []string{"snSlotsEntry"}, Children: []*Node{ {Oid: "1.1", Label: "snSlotsA", Indexes: []string{"snSlotsEntry"}}}, }, }, // MAC Address type set. { in: &Node{Oid: "1", Label: "mac", Hint: "1x:"}, out: &Node{Oid: "1", Label: "mac", Hint: "1x:", Type: "PhysAddress48"}, }, // Short ASCII string. { in: &Node{Oid: "1", Label: "ascii", Hint: "32a"}, out: &Node{Oid: "1", Label: "ascii", Hint: "32a", Type: "DisplayString"}, }, // DisplayString referencing RFC1213. { in: &Node{Oid: "1", Label: "ascii", TextualConvention: "DisplayString"}, out: &Node{Oid: "1", Label: "ascii", TextualConvention: "DisplayString", Type: "DisplayString"}, }, // PhysAddress referencing RFC1213. { in: &Node{Oid: "1", Label: "mac", TextualConvention: "PhysAddress"}, out: &Node{Oid: "1", Label: "mac", TextualConvention: "PhysAddress", Type: "PhysAddress48"}, }, // UTF-8 string. { in: &Node{Oid: "1", Label: "utf8", Hint: "255t"}, out: &Node{Oid: "1", Label: "utf8", Hint: "255t", Type: "DisplayString"}, }, // Mix of number and ASCII string. { in: &Node{Oid: "1", Label: "notascii", Hint: "2d32a", Type: "OCTETSTR"}, out: &Node{Oid: "1", Label: "notascii", Hint: "2d32a", Type: "OCTETSTR"}, }, // Opaques converted. { in: &Node{Oid: "1", Type: "OPAQUE", TextualConvention: "Float"}, out: &Node{Oid: "1", Type: "Float", TextualConvention: "Float"}, }, { in: &Node{Oid: "1", Type: "OPAQUE", TextualConvention: "Double"}, out: &Node{Oid: "1", Type: "Double", TextualConvention: "Double"}, }, // RFC 2579 DateAndTime. { in: &Node{Oid: "1", Type: "DisplayString", TextualConvention: "DateAndTime"}, out: &Node{Oid: "1", Type: "DateAndTime", TextualConvention: "DateAndTime"}, }, // RFC 4100 InetAddress conventions. { in: &Node{Oid: "1", Type: "OctectString", TextualConvention: "InetAddressIPv4"}, out: &Node{Oid: "1", Type: "InetAddressIPv4", TextualConvention: "InetAddressIPv4"}, }, { in: &Node{Oid: "1", Type: "OctectString", TextualConvention: "InetAddressIPv6"}, out: &Node{Oid: "1", Type: "InetAddressIPv6", TextualConvention: "InetAddressIPv6"}, }, { in: &Node{Oid: "1", Type: "OctectString", TextualConvention: "InetAddress"}, out: &Node{Oid: "1", Type: "InetAddress", TextualConvention: "InetAddress"}, }, } for i, c := range cases { // Indexes always end up initialized. walkNode(c.out, func(n *Node) { if n.Indexes == nil { n.Indexes = []string{} } }) prepareTree(c.in, log.NewNopLogger()) if !reflect.DeepEqual(c.in, c.out) { t.Errorf("prepareTree: difference in case %d", i) walkNode(c.in, func(n *Node) { t.Errorf("Got: %+v", n) }) walkNode(c.out, func(n *Node) { t.Errorf("Wanted: %+v\n\n", n) }) } } } func TestGenerateConfigModule(t *testing.T) { var regexpFooBar config.Regexp regexpFooBar.Regexp, _ = regexp.Compile(".*") strMetrics := make(map[string][]config.RegexpExtract) strMetrics["Status"] = []config.RegexpExtract{ { Regex: regexpFooBar, Value: "5", }, } overrides := make(map[string]MetricOverrides) overrides["root"] = MetricOverrides{ RegexpExtracts: strMetrics, } cases := []struct { node *Node cfg *ModuleConfig // SNMP generator config. out *config.Module // SNMP exporter config. }{ // Simple metric with Regexp override. { node: &Node{Oid: "1", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "root"}, cfg: &ModuleConfig{ Walk: []string{"root"}, Overrides: overrides, }, out: &config.Module{ Get: []string{"1.0"}, Metrics: []*config.Metric{ { Name: "root", Oid: "1", Type: "gauge", Help: " - 1", RegexpExtracts: strMetrics, }, }, }, }, // Simple metric. { node: &Node{Oid: "1", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "root"}, cfg: &ModuleConfig{ Walk: []string{"root"}, }, out: &config.Module{ Get: []string{"1.0"}, Metrics: []*config.Metric{ { Name: "root", Oid: "1", Type: "gauge", Help: " - 1", }, }, }, }, // Simple walk. { node: &Node{Oid: "1", Type: "OTHER", Label: "root", Children: []*Node{ {Oid: "1.1", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "node"}, }}, cfg: &ModuleConfig{ Walk: []string{"root"}, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "node", Oid: "1.1", Type: "gauge", Help: " - 1.1", }, }, }, }, // Can also provide OIDs to get. { node: &Node{Oid: "1", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "root"}, cfg: &ModuleConfig{ Walk: []string{"1"}, }, out: &config.Module{ Get: []string{"1.0"}, Metrics: []*config.Metric{ { Name: "root", Oid: "1", Type: "gauge", Help: " - 1", }, }, }, }, // Duplicate walks handled gracefully. { node: &Node{Oid: "1", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "root"}, cfg: &ModuleConfig{ Walk: []string{"1", "root"}, }, out: &config.Module{ Get: []string{"1.0"}, Metrics: []*config.Metric{ { Name: "root", Oid: "1", Type: "gauge", Help: " - 1", }, }, }, }, // Scalar root with instance child. { node: &Node{Oid: "1", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "root", Children: []*Node{ {Oid: "1.0", Type: "OTHER", Label: "rootInstance"}, }}, cfg: &ModuleConfig{ Walk: []string{"root"}, }, out: &config.Module{ Get: []string{"1.0"}, Metrics: []*config.Metric{ { Name: "root", Oid: "1", Type: "gauge", Help: " - 1", }, }, }, }, // Metric types. { node: &Node{Oid: "1", Type: "OTHER", Label: "root", Children: []*Node{ {Oid: "1.1", Access: "ACCESS_READONLY", Label: "OBJID", Type: "OBJID"}, {Oid: "1.2", Access: "ACCESS_READONLY", Label: "OCTETSTR", Type: "OCTETSTR"}, {Oid: "1.3", Access: "ACCESS_READONLY", Label: "INTEGER", Type: "INTEGER"}, {Oid: "1.4", Access: "ACCESS_READONLY", Label: "NETADDR", Type: "NETADDR"}, {Oid: "1.5", Access: "ACCESS_READONLY", Label: "IPADDR", Type: "IPADDR"}, {Oid: "1.6", Access: "ACCESS_READONLY", Label: "COUNTER", Type: "COUNTER"}, {Oid: "1.7", Access: "ACCESS_READONLY", Label: "GAUGE", Type: "GAUGE"}, {Oid: "1.8", Access: "ACCESS_READONLY", Label: "TIMETICKS", Type: "TIMETICKS"}, {Oid: "1.9", Access: "ACCESS_READONLY", Label: "OPAQUE", Type: "OPAQUE"}, {Oid: "1.10", Access: "ACCESS_READONLY", Label: "NULL", Type: "NULL"}, {Oid: "1.11", Access: "ACCESS_READONLY", Label: "COUNTER64", Type: "COUNTER64"}, {Oid: "1.12", Access: "ACCESS_READONLY", Label: "BITSTRING", Type: "BITSTRING"}, {Oid: "1.13", Access: "ACCESS_READONLY", Label: "NSAPADDRESS", Type: "NSAPADDRESS"}, {Oid: "1.14", Access: "ACCESS_READONLY", Label: "UINTEGER", Type: "UINTEGER"}, {Oid: "1.15", Access: "ACCESS_READONLY", Label: "UNSIGNED32", Type: "UNSIGNED32"}, {Oid: "1.16", Access: "ACCESS_READONLY", Label: "INTEGER32", Type: "INTEGER32"}, {Oid: "1.20", Access: "ACCESS_READONLY", Label: "TRAPTYPE", Type: "TRAPTYPE"}, {Oid: "1.21", Access: "ACCESS_READONLY", Label: "NOTIFTYPE", Type: "NOTIFTYPE"}, {Oid: "1.22", Access: "ACCESS_READONLY", Label: "OBJGROUP", Type: "OBJGROUP"}, {Oid: "1.23", Access: "ACCESS_READONLY", Label: "NOTIFGROUP", Type: "NOTIFGROUP"}, {Oid: "1.24", Access: "ACCESS_READONLY", Label: "MODID", Type: "MODID"}, {Oid: "1.25", Access: "ACCESS_READONLY", Label: "AGENTCAP", Type: "AGENTCAP"}, {Oid: "1.26", Access: "ACCESS_READONLY", Label: "MODCOMP", Type: "MODCOMP"}, {Oid: "1.27", Access: "ACCESS_READONLY", Label: "OBJIDENTITY", Type: "OBJIDENTITY"}, {Oid: "1.100", Access: "ACCESS_READONLY", Label: "MacAddress", Type: "OCTETSTR", Hint: "1x:"}, {Oid: "1.200", Access: "ACCESS_READONLY", Label: "Float", Type: "OPAQUE", TextualConvention: "Float"}, {Oid: "1.201", Access: "ACCESS_READONLY", Label: "Double", Type: "OPAQUE", TextualConvention: "Double"}, {Oid: "1.202", Access: "ACCESS_READONLY", Label: "DateAndTime", Type: "DisplayString", TextualConvention: "DateAndTime"}, {Oid: "1.203", Access: "ACCESS_READONLY", Label: "InetAddressIPv4", Type: "OCTETSTR", TextualConvention: "InetAddressIPv4"}, {Oid: "1.204", Access: "ACCESS_READONLY", Label: "InetAddressIPv6", Type: "OCTETSTR", TextualConvention: "InetAddressIPv6"}, }}, cfg: &ModuleConfig{ Walk: []string{"root", "1.3"}, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "OCTETSTR", Oid: "1.2", Type: "OctetString", Help: " - 1.2", }, { Name: "INTEGER", Oid: "1.3", Type: "gauge", Help: " - 1.3", }, { Name: "NETADDR", Oid: "1.4", Type: "InetAddressIPv4", Help: " - 1.4", }, { Name: "IPADDR", Oid: "1.5", Type: "InetAddressIPv4", Help: " - 1.5", }, { Name: "COUNTER", Oid: "1.6", Type: "counter", Help: " - 1.6", }, { Name: "GAUGE", Oid: "1.7", Type: "gauge", Help: " - 1.7", }, { Name: "TIMETICKS", Oid: "1.8", Type: "gauge", Help: " - 1.8", }, { Name: "COUNTER64", Oid: "1.11", Type: "counter", Help: " - 1.11", }, { Name: "BITSTRING", Oid: "1.12", Type: "Bits", Help: " - 1.12", }, { Name: "UINTEGER", Oid: "1.14", Type: "gauge", Help: " - 1.14", }, { Name: "UNSIGNED32", Oid: "1.15", Type: "gauge", Help: " - 1.15", }, { Name: "INTEGER32", Oid: "1.16", Type: "gauge", Help: " - 1.16", }, { Name: "MacAddress", Oid: "1.100", Type: "PhysAddress48", Help: " - 1.100", }, { Name: "Float", Oid: "1.200", Type: "Float", Help: " - 1.200", }, { Name: "Double", Oid: "1.201", Type: "Double", Help: " - 1.201", }, { Name: "DateAndTime", Oid: "1.202", Type: "DateAndTime", Help: " - 1.202", }, { Name: "InetAddressIPv4", Oid: "1.203", Type: "InetAddressIPv4", Help: " - 1.203", }, { Name: "InetAddressIPv6", Oid: "1.204", Type: "InetAddressIPv6", Help: " - 1.204", }, }, }, }, // Simple metric with ignore override. { node: &Node{Oid: "1", Type: "OTHER", Label: "root", Children: []*Node{ {Oid: "1.1", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "node1"}, {Oid: "1.2", Access: "ACCESS_READONLY", Type: "OCTETSTR", Label: "node2"}, }}, cfg: &ModuleConfig{ Walk: []string{"root"}, Overrides: map[string]MetricOverrides{ "node2": MetricOverrides{Ignore: true}, }, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "node1", Oid: "1.1", Type: "gauge", Help: " - 1.1", }, }, }, }, // Simple metric with type override. { node: &Node{Oid: "1", Type: "OTHER", Label: "root", Children: []*Node{ {Oid: "1.1", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "node1"}, {Oid: "1.2", Access: "ACCESS_READONLY", Type: "OCTETSTR", Label: "node2"}, }}, cfg: &ModuleConfig{ Walk: []string{"root"}, Overrides: map[string]MetricOverrides{ "node2": MetricOverrides{Type: "DisplayString"}, }, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "node1", Oid: "1.1", Type: "gauge", Help: " - 1.1", }, { Name: "node2", Oid: "1.2", Type: "DisplayString", Help: " - 1.2", }, }, }, }, // Enums { node: &Node{Oid: "1", Type: "OTHER", Label: "root", Children: []*Node{ {Oid: "1.1", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "node1", EnumValues: map[int]string{0: "a"}}, {Oid: "1.2", Access: "ACCESS_READONLY", Type: "INTEGER", Label: "node2", EnumValues: map[int]string{0: "b"}}, }}, cfg: &ModuleConfig{ Walk: []string{"root"}, Overrides: map[string]MetricOverrides{ "node1": MetricOverrides{Type: "EnumAsInfo"}, "node2": MetricOverrides{Type: "EnumAsStateSet"}, }, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "node1", Oid: "1.1", Type: "EnumAsInfo", Help: " - 1.1", EnumValues: map[int]string{0: "a"}, }, { Name: "node2", Oid: "1.2", Type: "EnumAsStateSet", Help: " - 1.2", EnumValues: map[int]string{0: "b"}, }, }, }, }, // Table with type override. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"node1"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "node1", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "node2", Type: "OCTETSTR"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1"}, Overrides: map[string]MetricOverrides{ "node1": MetricOverrides{Type: "counter"}, "node2": MetricOverrides{Type: "DisplayString"}, }, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "node1", Oid: "1.1.1.1", Type: "counter", Help: " - 1.1.1.1", Indexes: []*config.Index{ { Labelname: "node1", Type: "counter", }, }, }, { Name: "node2", Oid: "1.1.1.2", Type: "DisplayString", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "node1", Type: "counter", }, }, }, }, }, }, // Tables with accessible & inaccessible. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_NOACCESS", Label: "tableNoAccess", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_CREATE", Label: "tableCreate", Type: "INTEGER"}, {Oid: "1.1.1.3", Access: "ACCESS_WRITEONLY", Label: "tableWriteOnly", Type: "INTEGER"}, {Oid: "1.1.1.4", Access: "ACCESS_READONLY", Label: "tableReadOnly", Type: "INTEGER"}, {Oid: "1.1.1.5", Access: "ACCESS_READWRITE", Label: "tableReadWrite", Type: "INTEGER"}, {Oid: "1.1.1.6", Access: "ACCESS_NOTIFY", Label: "tableNotify", Type: "INTEGER"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1"}, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "tableNoAccess", Oid: "1.1.1.1", Type: "gauge", Help: " - 1.1.1.1", }, { Name: "tableCreate", Oid: "1.1.1.2", Type: "gauge", Help: " - 1.1.1.2", }, { Name: "tableReadOnly", Oid: "1.1.1.4", Type: "gauge", Help: " - 1.1.1.4", }, { Name: "tableReadWrite", Oid: "1.1.1.5", Type: "gauge", Help: " - 1.1.1.5", }, }, }, }, // Basic table with integer index. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableFoo", Type: "INTEGER"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1"}, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "tableIndex", Oid: "1.1.1.1", Type: "gauge", Help: " - 1.1.1.1", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, { Name: "tableFoo", Oid: "1.1.1.2", Type: "gauge", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, }, }, }, // Tables with non-integer indexes. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "octet", Children: []*Node{ {Oid: "1.1.1", Label: "octetEntry", Indexes: []string{"octetIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "octetIndex", Type: "OCTETSTR"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "octetFoo", Type: "INTEGER"}}}}}, {Oid: "1.2", Label: "bitstring", Children: []*Node{ {Oid: "1.2.1", Label: "bitstringEntry", Indexes: []string{"bitstringIndex"}, Children: []*Node{ {Oid: "1.2.1.1", Access: "ACCESS_READONLY", Label: "bitstringIndex", Type: "BITSTRING"}, {Oid: "1.2.1.2", Access: "ACCESS_READONLY", Label: "bitstringFoo", Type: "INTEGER"}}}}}, {Oid: "1.3", Label: "ipaddr", Children: []*Node{ {Oid: "1.3.1", Label: "ipaddrEntry", Indexes: []string{"ipaddrIndex"}, Children: []*Node{ {Oid: "1.3.1.1", Access: "ACCESS_READONLY", Label: "ipaddrIndex", Type: "IPADDR"}, {Oid: "1.3.1.2", Access: "ACCESS_READONLY", Label: "ipaddrFoo", Type: "INTEGER"}}}}}, {Oid: "1.4", Label: "netaddr", Children: []*Node{ {Oid: "1.4.1", Label: "netaddrEntry", Indexes: []string{"netaddrIndex"}, Children: []*Node{ {Oid: "1.4.1.1", Access: "ACCESS_READONLY", Label: "netaddrIndex", Type: "NETADDR"}, {Oid: "1.4.1.2", Access: "ACCESS_READONLY", Label: "netaddrFoo", Type: "INTEGER"}}}}}, {Oid: "1.5", Label: "physaddress48", Children: []*Node{ {Oid: "1.5.1", Label: "physaddress48Entry", Indexes: []string{"physaddress48Index"}, Children: []*Node{ {Oid: "1.5.1.1", Access: "ACCESS_READONLY", Label: "physaddress48Index", Type: "OCTETSTR", Hint: "1x:"}, {Oid: "1.5.1.2", Access: "ACCESS_READONLY", Label: "physaddress48Foo", Type: "INTEGER"}}}}}, {Oid: "1.6", Label: "fixedSize", Children: []*Node{ {Oid: "1.6.1", Label: "fixedSizeEntry", Indexes: []string{"fixedSizeIndex"}, Children: []*Node{ {Oid: "1.6.1.1", Access: "ACCESS_READONLY", Label: "fixedSizeIndex", Type: "OCTETSTR", FixedSize: 8}, {Oid: "1.6.1.2", Access: "ACCESS_READONLY", Label: "fixedSizeFoo", Type: "INTEGER"}}}}}, {Oid: "1.7", Label: "impliedSize", Children: []*Node{ {Oid: "1.7.1", Label: "impliedSizeEntry", Indexes: []string{"impliedSizeIndex"}, ImpliedIndex: true, Children: []*Node{ {Oid: "1.7.1.1", Access: "ACCESS_READONLY", Label: "impliedSizeIndex", Type: "OCTETSTR"}, {Oid: "1.7.1.2", Access: "ACCESS_READONLY", Label: "impliedSizeFoo", Type: "INTEGER"}}}}}, {Oid: "1.8", Label: "ipv4", Children: []*Node{ {Oid: "1.8.1", Label: "ipv4Entry", Indexes: []string{"ipv4Index"}, Children: []*Node{ {Oid: "1.8.1.1", Access: "ACCESS_READONLY", Label: "ipv4Index", Type: "OCTETSTR", TextualConvention: "InetAddressIPv4"}, {Oid: "1.8.1.2", Access: "ACCESS_READONLY", Label: "ipv4Foo", Type: "INTEGER"}}}}}, {Oid: "1.9", Label: "ipv6", Children: []*Node{ {Oid: "1.9.1", Label: "ipv6Entry", Indexes: []string{"ipv6Index"}, Children: []*Node{ {Oid: "1.9.1.1", Access: "ACCESS_READONLY", Label: "ipv6Index", Type: "OCTETSTR", TextualConvention: "InetAddressIPv6"}, {Oid: "1.9.1.2", Access: "ACCESS_READONLY", Label: "ipv6Foo", Type: "INTEGER"}}}}}, }}, cfg: &ModuleConfig{ Walk: []string{"1"}, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "octetIndex", Oid: "1.1.1.1", Help: " - 1.1.1.1", Type: "OctetString", Indexes: []*config.Index{ { Labelname: "octetIndex", Type: "OctetString", }, }, }, { Name: "octetFoo", Oid: "1.1.1.2", Help: " - 1.1.1.2", Type: "gauge", Indexes: []*config.Index{ { Labelname: "octetIndex", Type: "OctetString", }, }, }, { Name: "bitstringIndex", Oid: "1.2.1.1", Help: " - 1.2.1.1", Type: "Bits", Indexes: []*config.Index{ { Labelname: "bitstringIndex", Type: "Bits", }, }, }, { Name: "bitstringFoo", Oid: "1.2.1.2", Help: " - 1.2.1.2", Type: "gauge", Indexes: []*config.Index{ { Labelname: "bitstringIndex", Type: "Bits", }, }, }, { Name: "ipaddrIndex", Oid: "1.3.1.1", Help: " - 1.3.1.1", Type: "InetAddressIPv4", Indexes: []*config.Index{ { Labelname: "ipaddrIndex", Type: "InetAddressIPv4", }, }, }, { Name: "ipaddrFoo", Oid: "1.3.1.2", Help: " - 1.3.1.2", Type: "gauge", Indexes: []*config.Index{ { Labelname: "ipaddrIndex", Type: "InetAddressIPv4", }, }, }, { Name: "netaddrIndex", Oid: "1.4.1.1", Help: " - 1.4.1.1", Type: "InetAddressIPv4", Indexes: []*config.Index{ { Labelname: "netaddrIndex", Type: "InetAddressIPv4", }, }, }, { Name: "netaddrFoo", Oid: "1.4.1.2", Help: " - 1.4.1.2", Type: "gauge", Indexes: []*config.Index{ { Labelname: "netaddrIndex", Type: "InetAddressIPv4", }, }, }, { Name: "physaddress48Index", Oid: "1.5.1.1", Help: " - 1.5.1.1", Type: "PhysAddress48", Indexes: []*config.Index{ { Labelname: "physaddress48Index", Type: "PhysAddress48", }, }, }, { Name: "physaddress48Foo", Oid: "1.5.1.2", Help: " - 1.5.1.2", Type: "gauge", Indexes: []*config.Index{ { Labelname: "physaddress48Index", Type: "PhysAddress48", }, }, }, { Name: "fixedSizeIndex", Oid: "1.6.1.1", Help: " - 1.6.1.1", Type: "OctetString", Indexes: []*config.Index{ { Labelname: "fixedSizeIndex", Type: "OctetString", FixedSize: 8, }, }, }, { Name: "fixedSizeFoo", Oid: "1.6.1.2", Help: " - 1.6.1.2", Type: "gauge", Indexes: []*config.Index{ { Labelname: "fixedSizeIndex", Type: "OctetString", FixedSize: 8, }, }, }, { Name: "impliedSizeIndex", Oid: "1.7.1.1", Help: " - 1.7.1.1", Type: "OctetString", Indexes: []*config.Index{ { Labelname: "impliedSizeIndex", Type: "OctetString", Implied: true, }, }, }, { Name: "impliedSizeFoo", Oid: "1.7.1.2", Help: " - 1.7.1.2", Type: "gauge", Indexes: []*config.Index{ { Labelname: "impliedSizeIndex", Type: "OctetString", Implied: true, }, }, }, { Name: "ipv4Index", Oid: "1.8.1.1", Help: " - 1.8.1.1", Type: "InetAddressIPv4", Indexes: []*config.Index{ { Labelname: "ipv4Index", Type: "InetAddressIPv4", }, }, }, { Name: "ipv4Foo", Oid: "1.8.1.2", Help: " - 1.8.1.2", Type: "gauge", Indexes: []*config.Index{ { Labelname: "ipv4Index", Type: "InetAddressIPv4", }, }, }, { Name: "ipv6Index", Oid: "1.9.1.1", Help: " - 1.9.1.1", Type: "InetAddressIPv6", Indexes: []*config.Index{ { Labelname: "ipv6Index", Type: "InetAddressIPv6", }, }, }, { Name: "ipv6Foo", Oid: "1.9.1.2", Help: " - 1.9.1.2", Type: "gauge", Indexes: []*config.Index{ { Labelname: "ipv6Index", Type: "InetAddressIPv6", }, }, }, }, }, }, // One table lookup, lookup not walked, labels kept. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "octet", Children: []*Node{ {Oid: "1.1.1", Label: "octetEntry", Indexes: []string{"octetIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "octetIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "octetDesc", Type: "OCTETSTR"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "octetFoo", Type: "INTEGER"}}}}}}}, cfg: &ModuleConfig{ Walk: []string{"octetFoo"}, Lookups: []*Lookup{ { SourceIndexes: []string{"octetIndex"}, Lookup: "octetDesc", }, }, }, out: &config.Module{ // Walk is expanded to include the lookup OID. Walk: []string{"1.1.1.2", "1.1.1.3"}, Metrics: []*config.Metric{ { Name: "octetFoo", Oid: "1.1.1.3", Help: " - 1.1.1.3", Type: "gauge", Indexes: []*config.Index{ { Labelname: "octetIndex", Type: "gauge", }, }, Lookups: []*config.Lookup{ { Labels: []string{"octetIndex"}, Labelname: "octetDesc", Type: "OctetString", Oid: "1.1.1.2", }, }, }, }, }, }, // One table lookup, lookup not walked. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "octet", Children: []*Node{ {Oid: "1.1.1", Label: "octetEntry", Indexes: []string{"octetIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "octetIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "octetDesc", Type: "OCTETSTR"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "octetFoo", Type: "INTEGER"}}}}}}}, cfg: &ModuleConfig{ Walk: []string{"octetFoo"}, Lookups: []*Lookup{ { SourceIndexes: []string{"octetIndex"}, Lookup: "octetDesc", DropSourceIndexes: true, }, }, }, out: &config.Module{ // Walk is expanded to include the lookup OID. Walk: []string{"1.1.1.2", "1.1.1.3"}, Metrics: []*config.Metric{ { Name: "octetFoo", Oid: "1.1.1.3", Help: " - 1.1.1.3", Type: "gauge", Indexes: []*config.Index{ { Labelname: "octetIndex", Type: "gauge", }, }, Lookups: []*config.Lookup{ { Labels: []string{"octetIndex"}, Labelname: "octetDesc", Type: "OctetString", Oid: "1.1.1.2", }, { Labelname: "octetIndex", }, }, }, }, }, }, // Lookup via OID. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "octet", Children: []*Node{ {Oid: "1.1.1", Label: "octetEntry", Indexes: []string{"octetIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "octetIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "octetDesc", Type: "OCTETSTR"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "octetFoo", Type: "INTEGER"}}}}}}}, cfg: &ModuleConfig{ Walk: []string{"octetFoo"}, Lookups: []*Lookup{ { SourceIndexes: []string{"octetIndex"}, Lookup: "1.1.1.2", DropSourceIndexes: true, }, }, }, out: &config.Module{ // Walk is expanded to include the lookup OID. Walk: []string{"1.1.1.2", "1.1.1.3"}, Metrics: []*config.Metric{ { Name: "octetFoo", Oid: "1.1.1.3", Help: " - 1.1.1.3", Type: "gauge", Indexes: []*config.Index{ { Labelname: "octetIndex", Type: "gauge", }, }, Lookups: []*config.Lookup{ { Labels: []string{"octetIndex"}, Labelname: "octetDesc", Type: "OctetString", Oid: "1.1.1.2", }, { Labelname: "octetIndex", }, }, }, }, }, }, // Multi-index table lookup, lookup not walked. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "octet", Children: []*Node{ {Oid: "1.1.1", Label: "octetEntry", Indexes: []string{"octetIndex", "octetIndex2"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "octetIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "octetIndex2", Type: "INTEGER"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "octetDesc", Type: "OCTETSTR"}, {Oid: "1.1.1.4", Access: "ACCESS_READONLY", Label: "octetFoo", Type: "INTEGER"}}}}}}}, cfg: &ModuleConfig{ Walk: []string{"octetFoo"}, Lookups: []*Lookup{ { SourceIndexes: []string{"octetIndex", "octetIndex2"}, Lookup: "octetDesc", DropSourceIndexes: true, }, }, }, out: &config.Module{ // Walk is expanded to include the lookup OID. Walk: []string{"1.1.1.3", "1.1.1.4"}, Metrics: []*config.Metric{ { Name: "octetFoo", Oid: "1.1.1.4", Help: " - 1.1.1.4", Type: "gauge", Indexes: []*config.Index{ { Labelname: "octetIndex", Type: "gauge", }, { Labelname: "octetIndex2", Type: "gauge", }, }, Lookups: []*config.Lookup{ { Labels: []string{"octetIndex", "octetIndex2"}, Labelname: "octetDesc", Type: "OctetString", Oid: "1.1.1.3", }, { Labelname: "octetIndex", }, { Labelname: "octetIndex2", }, }, }, }, }, }, // Validate metric names. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Access: "ACCESS_READONLY", Label: "digital-sen1-1", Hint: "1x:"}, }}, cfg: &ModuleConfig{ Walk: []string{"root"}, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "digital_sen1_1", Oid: "1.1", Type: "PhysAddress48", Help: " - 1.1", Indexes: []*config.Index{}, Lookups: []*config.Lookup{}, }, }, }, }, // Validate label names. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "octet", Children: []*Node{ {Oid: "1.1.1", Label: "octet-Entry", Indexes: []string{"octet&Index"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "octet&Index", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "octet*Desc", Type: "OCTETSTR"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "octet^Foo", Type: "INTEGER"}}}}}}}, cfg: &ModuleConfig{ Walk: []string{"octet^Foo"}, Lookups: []*Lookup{ { SourceIndexes: []string{"octet&Index"}, Lookup: "1.1.1.2", DropSourceIndexes: true, }, }, }, out: &config.Module{ // Walk is expanded to include the lookup OID. Walk: []string{"1.1.1.2", "1.1.1.3"}, Metrics: []*config.Metric{ { Name: "octet_Foo", Oid: "1.1.1.3", Type: "gauge", Help: " - 1.1.1.3", Indexes: []*config.Index{ { Labelname: "octet_Index", Type: "gauge", }, }, Lookups: []*config.Lookup{ { Labels: []string{"octet_Index"}, Labelname: "octet_Desc", Type: "OctetString", Oid: "1.1.1.2", }, { Labelname: "octet_Index", }, }, }, }, }, }, // Validate table and instance conflict. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableFoo", Type: "INTEGER"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.2.100", "1.1.1.2"}, }, out: &config.Module{ Walk: []string{"1.1.1.2"}, Metrics: []*config.Metric{ { Name: "tableFoo", Oid: "1.1.1.2", Type: "gauge", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, }, }, }, // Validate table instances. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableFoo", Type: "INTEGER"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.2.100", "1.1.1.2.200"}, }, out: &config.Module{ Get: []string{"1.1.1.2.100", "1.1.1.2.200"}, // Single metric. Metrics: []*config.Metric{ { Name: "tableFoo", Oid: "1.1.1.2", Type: "gauge", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, }, }, }, // Validate multiple rows of table instances. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableFoo", Type: "INTEGER"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "tableDesc", Type: "OCTETSTR"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.2.100", "1.1.1.3.200"}, }, out: &config.Module{ Get: []string{"1.1.1.2.100", "1.1.1.3.200"}, Metrics: []*config.Metric{ { Name: "tableFoo", Oid: "1.1.1.2", Type: "gauge", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, { Name: "tableDesc", Oid: "1.1.1.3", Type: "OctetString", Help: " - 1.1.1.3", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, }, }, }, // Validate table instances with lookup not walked. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableFoo", Type: "INTEGER"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "tableDesc", Type: "OCTETSTR"}, {Oid: "1.1.1.4", Access: "ACCESS_READONLY", Label: "tableBar", Type: "INTEGER"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.2.100", "1.1.1.4.100", "1.1.1.2.200"}, Lookups: []*Lookup{ { SourceIndexes: []string{"tableIndex"}, Lookup: "tableDesc", DropSourceIndexes: true, }, }, }, out: &config.Module{ Get: []string{"1.1.1.2.100", "1.1.1.2.200", "1.1.1.3.100", "1.1.1.3.200", "1.1.1.4.100"}, Metrics: []*config.Metric{ { Name: "tableFoo", Oid: "1.1.1.2", Type: "gauge", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, Lookups: []*config.Lookup{ { Labels: []string{"tableIndex"}, Labelname: "tableDesc", Type: "OctetString", Oid: "1.1.1.3", }, { Labelname: "tableIndex", }, }, }, { Name: "tableBar", Oid: "1.1.1.4", Type: "gauge", Help: " - 1.1.1.4", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, Lookups: []*config.Lookup{ { Labels: []string{"tableIndex"}, Labelname: "tableDesc", Type: "OctetString", Oid: "1.1.1.3", }, { Labelname: "tableIndex", }, }, }, }, }, }, // Validate specific table instances with lookup walked. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableFoo", Type: "INTEGER"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "tableDesc", Type: "OCTETSTR"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.2.100", "1.1.1.3"}, Lookups: []*Lookup{ { SourceIndexes: []string{"tableIndex"}, Lookup: "tableDesc", DropSourceIndexes: true, }, }, }, out: &config.Module{ Get: []string{"1.1.1.2.100"}, Walk: []string{"1.1.1.3"}, Metrics: []*config.Metric{ { Name: "tableFoo", Oid: "1.1.1.2", Type: "gauge", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, Lookups: []*config.Lookup{ { Labels: []string{"tableIndex"}, Labelname: "tableDesc", Type: "OctetString", Oid: "1.1.1.3", }, { Labelname: "tableIndex", }, }, }, { Name: "tableDesc", Oid: "1.1.1.3", Type: "OctetString", Help: " - 1.1.1.3", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, Lookups: []*config.Lookup{ { Labels: []string{"tableIndex"}, Labelname: "tableDesc", Type: "OctetString", Oid: "1.1.1.3", }, { Labelname: "tableIndex", }, }, }, }, }, }, // Table with InetAddressType and valid InetAddress. // InetAddressType is added to walk. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "InetAddressType"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "InetAddress"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.3"}, }, out: &config.Module{ Walk: []string{"1.1.1.2", "1.1.1.3"}, Metrics: []*config.Metric{ { Name: "tableAddr", Oid: "1.1.1.3", Type: "InetAddress", Help: " - 1.1.1.3", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, }, }, }, // Table with InetAddressType and valid InetAddress instance. // InetAddressType is added to walk. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "InetAddressType"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "InetAddress"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.3.42"}, }, out: &config.Module{ Get: []string{"1.1.1.2.42", "1.1.1.3.42"}, Metrics: []*config.Metric{ { Name: "tableAddr", Oid: "1.1.1.3", Type: "InetAddress", Help: " - 1.1.1.3", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, }, }, }, // Table with InetAddressType and InetAddress in the wrong order. // InetAddress becomes OctetString. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "InetAddress"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "InetAddressType"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.2"}, }, out: &config.Module{ Walk: []string{"1.1.1.2"}, Metrics: []*config.Metric{ { Name: "tableAddr", Oid: "1.1.1.2", Type: "OctetString", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, }, }, }, // Table with InetAddressType and InetAddress index. // Index becomes just InetAddress. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableAddrType", "tableAddr"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "InetAddressType"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "InetAddress"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1"}, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "tableAddrType", Oid: "1.1.1.1", Type: "gauge", Help: " - 1.1.1.1", Indexes: []*config.Index{ { Labelname: "tableAddr", Type: "InetAddress", }, }, }, { Name: "tableAddr", Oid: "1.1.1.2", Type: "InetAddress", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableAddr", Type: "InetAddress", }, }, }, }, }, }, // Table with InetAddressType and InetAddress index in wrong order gets dropped. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableAddr", "tableAddrType"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "InetAddressType"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "InetAddress"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1"}, }, out: &config.Module{ Walk: []string{"1"}, }, }, // Table with InetAddressType and valid InetAddressMissingSize. // InetAddressType is added to walk. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "InetAddressType"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "InetAddress"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.3"}, Overrides: map[string]MetricOverrides{ "tableAddr": MetricOverrides{Type: "InetAddressMissingSize"}, }, }, out: &config.Module{ Walk: []string{"1.1.1.2", "1.1.1.3"}, Metrics: []*config.Metric{ { Name: "tableAddr", Oid: "1.1.1.3", Type: "InetAddressMissingSize", Help: " - 1.1.1.3", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, }, }, }, // Table with InetAddressType and InetAddressMissingSize in the wrong order. // InetAddressMissingSize becomes OctetString. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableIndex"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableIndex", Type: "INTEGER"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "InetAddress"}, {Oid: "1.1.1.3", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "InetAddressType"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1.1.1.2"}, Overrides: map[string]MetricOverrides{ "tableAddr": MetricOverrides{Type: "InetAddressMissingSize"}, }, }, out: &config.Module{ Walk: []string{"1.1.1.2"}, Metrics: []*config.Metric{ { Name: "tableAddr", Oid: "1.1.1.2", Type: "OctetString", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableIndex", Type: "gauge", }, }, }, }, }, }, // Table with InetAddressType and InetAddressMissingSize index. // Index becomes just InetAddressMissingSize. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableAddrType", "tableAddr"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "InetAddressType"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "InetAddress"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1"}, Overrides: map[string]MetricOverrides{ "tableAddr": MetricOverrides{Type: "InetAddressMissingSize"}, }, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "tableAddrType", Oid: "1.1.1.1", Type: "gauge", Help: " - 1.1.1.1", Indexes: []*config.Index{ { Labelname: "tableAddr", Type: "InetAddressMissingSize", }, }, }, { Name: "tableAddr", Oid: "1.1.1.2", Type: "InetAddressMissingSize", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableAddr", Type: "InetAddressMissingSize", }, }, }, }, }, }, // Table with InetAddressType and InetAddressMissingSize index in wrong order gets dropped. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableAddr", "tableAddrType"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "InetAddressType"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "InetAddress"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1"}, Overrides: map[string]MetricOverrides{ "tableAddr": MetricOverrides{Type: "InetAddressMissingSize"}, }, }, out: &config.Module{ Walk: []string{"1"}, }, }, // Table with LldpPortIdSubtype and LldpPortId index. // Index becomes just LldpPortId. { node: &Node{Oid: "1", Label: "root", Children: []*Node{ {Oid: "1.1", Label: "table", Children: []*Node{ {Oid: "1.1.1", Label: "tableEntry", Indexes: []string{"tableAddrType", "tableAddr"}, Children: []*Node{ {Oid: "1.1.1.1", Access: "ACCESS_READONLY", Label: "tableAddrType", Type: "INTEGER", TextualConvention: "LldpPortIdSubtype"}, {Oid: "1.1.1.2", Access: "ACCESS_READONLY", Label: "tableAddr", Type: "OCTETSTR", TextualConvention: "LldpPortId"}, }}}}}}, cfg: &ModuleConfig{ Walk: []string{"1"}, }, out: &config.Module{ Walk: []string{"1"}, Metrics: []*config.Metric{ { Name: "tableAddrType", Oid: "1.1.1.1", Type: "gauge", Help: " - 1.1.1.1", Indexes: []*config.Index{ { Labelname: "tableAddr", Type: "LldpPortId", }, }, }, { Name: "tableAddr", Oid: "1.1.1.2", Type: "LldpPortId", Help: " - 1.1.1.2", Indexes: []*config.Index{ { Labelname: "tableAddr", Type: "LldpPortId", }, }, }, }, }, }, } for i, c := range cases { // Indexes and lookups always end up initialized. for _, m := range c.out.Metrics { if m.Indexes == nil { m.Indexes = []*config.Index{} } if m.Lookups == nil { m.Lookups = []*config.Lookup{} } } nameToNode := prepareTree(c.node, log.NewNopLogger()) got, err := generateConfigModule(c.cfg, c.node, nameToNode, log.NewNopLogger()) if err != nil { t.Errorf("Error generating config in case %d: %s", i, err) } if !reflect.DeepEqual(got, c.out) { t.Errorf("GenerateConfigModule: difference in case %d", i) out, _ := yaml.Marshal(got) t.Errorf("Got: %s", out) out, _ = yaml.Marshal(c.out) t.Errorf("Wanted: %s", out) } } } prometheus-snmp-exporter-0.16.1+ds/go.mod000066400000000000000000000011741360361176200203330ustar00rootroot00000000000000module github.com/prometheus/snmp_exporter require ( github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect github.com/go-kit/kit v0.9.0 github.com/golang/mock v1.3.1 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/prometheus/client_golang v1.2.1 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 github.com/prometheus/common v0.7.0 github.com/soniah/gosnmp v1.22.0 golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v2 v2.2.4 ) go 1.13 prometheus-snmp-exporter-0.16.1+ds/go.sum000066400000000000000000000301051360361176200203540ustar00rootroot00000000000000github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soniah/gosnmp v1.22.0 h1:jVJi8+OGvR+JHIaZKMmnyNP0akJd2vEgNatybwhZvxg= github.com/soniah/gosnmp v1.22.0/go.mod h1:DuEpAS0az51+DyVBQwITDsoq4++e3LTNckp2GoasF2I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= prometheus-snmp-exporter-0.16.1+ds/main.go000066400000000000000000000155651360361176200205110ustar00rootroot00000000000000// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "net/http" _ "net/http/pprof" "os" "os/signal" "sync" "syscall" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/promlog" "github.com/prometheus/common/promlog/flag" "github.com/prometheus/common/version" "gopkg.in/alecthomas/kingpin.v2" yaml "gopkg.in/yaml.v2" "github.com/prometheus/snmp_exporter/config" ) var ( configFile = kingpin.Flag("config.file", "Path to configuration file.").Default("snmp.yml").String() listenAddress = kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry.").Default(":9116").String() dryRun = kingpin.Flag("dry-run", "Only verify configuration is valid and exit.").Default("false").Bool() // Metrics about the SNMP exporter itself. snmpDuration = prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: "snmp_collection_duration_seconds", Help: "Duration of collections by the SNMP exporter", }, []string{"module"}, ) snmpRequestErrors = prometheus.NewCounter( prometheus.CounterOpts{ Name: "snmp_request_errors_total", Help: "Errors in requests to the SNMP exporter", }, ) sc = &SafeConfig{ C: &config.Config{}, } reloadCh chan chan error ) func init() { prometheus.MustRegister(snmpDuration) prometheus.MustRegister(snmpRequestErrors) prometheus.MustRegister(version.NewCollector("snmp_exporter")) } func handler(w http.ResponseWriter, r *http.Request, logger log.Logger) { target := r.URL.Query().Get("target") if target == "" { http.Error(w, "'target' parameter must be specified", 400) snmpRequestErrors.Inc() return } moduleName := r.URL.Query().Get("module") if moduleName == "" { moduleName = "if_mib" } sc.RLock() module, ok := (*(sc.C))[moduleName] sc.RUnlock() if !ok { http.Error(w, fmt.Sprintf("Unknown module '%s'", moduleName), 400) snmpRequestErrors.Inc() return } logger = log.With(logger, "module", moduleName, "target", target) level.Debug(logger).Log("msg", "Starting scrape", "module") start := time.Now() registry := prometheus.NewRegistry() collector := collector{target: target, module: module, logger: logger} registry.MustRegister(collector) // Delegate http serving to Prometheus client library, which will call collector.Collect. h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) h.ServeHTTP(w, r) duration := time.Since(start).Seconds() snmpDuration.WithLabelValues(moduleName).Observe(duration) level.Debug(logger).Log("msg", "Finished scrape", "duration_seconds", duration) } func updateConfiguration(w http.ResponseWriter, r *http.Request) { switch r.Method { case "POST": rc := make(chan error) reloadCh <- rc if err := <-rc; err != nil { http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError) } default: http.Error(w, "POST method expected", 400) } } type SafeConfig struct { sync.RWMutex C *config.Config } func (sc *SafeConfig) ReloadConfig(configFile string) (err error) { conf, err := config.LoadFile(configFile) if err != nil { return err } sc.Lock() sc.C = conf sc.Unlock() return nil } func main() { promlogConfig := &promlog.Config{} flag.AddFlags(kingpin.CommandLine, promlogConfig) kingpin.HelpFlag.Short('h') kingpin.Parse() logger := promlog.New(promlogConfig) level.Info(logger).Log("msg", "Starting snmp_exporter", "version", version.Info()) level.Info(logger).Log("msg", "Build context", version.BuildContext()) // Bail early if the config is bad. var err error sc.C, err = config.LoadFile(*configFile) if err != nil { level.Error(logger).Log("msg", "Error parsing config file", "err", err) os.Exit(1) } // Exit if in dry-run mode. if *dryRun { level.Info(logger).Log("msg", "Configuration parsed successfully") return } // Initialize metrics. for module := range *sc.C { snmpDuration.WithLabelValues(module) } hup := make(chan os.Signal, 1) reloadCh = make(chan chan error) signal.Notify(hup, syscall.SIGHUP) go func() { for { select { case <-hup: if err := sc.ReloadConfig(*configFile); err != nil { level.Error(logger).Log("msg", "Error reloading config", "err", err) } else { level.Info(logger).Log("msg", "Loaded config file") } case rc := <-reloadCh: if err := sc.ReloadConfig(*configFile); err != nil { level.Error(logger).Log("msg", "Error reloading config", "err", err) rc <- err } else { level.Info(logger).Log("msg", "Loaded config file") rc <- nil } } } }() http.Handle("/metrics", promhttp.Handler()) // Normal metrics endpoint for SNMP exporter itself. // Endpoint to do SNMP scrapes. http.HandleFunc("/snmp", func(w http.ResponseWriter, r *http.Request) { handler(w, r, logger) }) http.HandleFunc("/-/reload", updateConfiguration) // Endpoint to reload configuration. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` SNMP Exporter

SNMP Exporter



Config

`)) }) http.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) { sc.RLock() c, err := yaml.Marshal(sc.C) sc.RUnlock() if err != nil { level.Error(logger).Log("msg", "Error marshaling configuration", "err", err) http.Error(w, err.Error(), 500) return } w.Write(c) }) level.Info(logger).Log("msg", "Listening on address", "address", *listenAddress) if err := http.ListenAndServe(*listenAddress, nil); err != nil { level.Error(logger).Log("msg", "Error starting HTTP server", "err", err) os.Exit(1) } } prometheus-snmp-exporter-0.16.1+ds/testdata/000077500000000000000000000000001360361176200210335ustar00rootroot00000000000000prometheus-snmp-exporter-0.16.1+ds/testdata/snmp-auth.yml000066400000000000000000000003141360361176200234700ustar00rootroot00000000000000module-auth-test: auth: community: mysecret security_level: SomethingReadOnly username: user password: mysecret auth_protocol: SHA priv_protocol: AES priv_password: mysecret prometheus-snmp-exporter-0.16.1+ds/testdata/snmp-with-overrides.yml000066400000000000000000000002231360361176200255010ustar00rootroot00000000000000default: walk: - 1.1.1.1.1.1 metrics: - name: testMetric oid: 1.1.1.1.1 type: gauge regex_extracts: Temp: - regex: