pax_global_header00006660000000000000000000000064145537555200014525gustar00rootroot0000000000000052 comment=0401ab9f809ca02709589cf90069afce42d10a10 prometheus-ipmi-exporter-1.8.0/000077500000000000000000000000001455375552000165305ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/.circleci/000077500000000000000000000000001455375552000203635ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/.circleci/config.yml000066400000000000000000000030331455375552000223520ustar00rootroot00000000000000--- version: 2.1 orbs: prometheus: prometheus/prometheus@0.17.1 executors: # Whenever the Go version is updated here, .promu.yml should # also be updated. golang: docker: - image: cimg/go:1.21 jobs: test: executor: golang steps: - prometheus/setup_environment - run: make - prometheus/store_artifact: file: ipmi_exporter codespell: docker: - image: circleci/python steps: - checkout - run: sudo pip install codespell - run: codespell --skip=".git,./vendor,ttar,go.mod,go.sum,*pem" -L uint,packages\',uptodate workflows: version: 2 ipmi_exporter: jobs: - test: filters: tags: only: /.*/ - prometheus/build: name: build filters: tags: only: /.*/ - codespell: filters: tags: only: /.*/ - prometheus/publish_master: context: org-context docker_hub_organization: prometheuscommunity quay_io_organization: prometheuscommunity requires: - test - build filters: branches: only: master - prometheus/publish_release: context: org-context docker_hub_organization: prometheuscommunity quay_io_organization: prometheuscommunity requires: - test - build filters: tags: only: /^v.*/ branches: ignore: /.*/ prometheus-ipmi-exporter-1.8.0/.github/000077500000000000000000000000001455375552000200705ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/.github/dependabot.yml000066400000000000000000000001561455375552000227220ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "monthly" prometheus-ipmi-exporter-1.8.0/.github/workflows/000077500000000000000000000000001455375552000221255ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/.github/workflows/golangci-lint.yml000066400000000000000000000017161455375552000254040ustar00rootroot00000000000000--- # This action is synced from https://github.com/prometheus/prometheus name: golangci-lint on: push: paths: - "go.sum" - "go.mod" - "**.go" - "scripts/errcheck_excludes.txt" - ".github/workflows/golangci-lint.yml" - ".golangci.yml" pull_request: jobs: golangci: name: lint runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: install Go uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0 with: go-version: 1.20.x - name: Install snmp_exporter/generator dependencies run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 with: version: v1.54.2 prometheus-ipmi-exporter-1.8.0/.gitignore000066400000000000000000000000361455375552000205170ustar00rootroot00000000000000build/ ipmi_exporter *.tar.gz prometheus-ipmi-exporter-1.8.0/.golangci.yml000066400000000000000000000003241455375552000211130ustar00rootroot00000000000000--- issues: exclude-rules: - path: _test.go linters: - errcheck linters-settings: errcheck: exclude-functions: # Never check for logger errors. - (github.com/go-kit/log.Logger).Log prometheus-ipmi-exporter-1.8.0/.promu.yml000066400000000000000000000017541455375552000205020ustar00rootroot00000000000000go: # Whenever the Go version is updated here, # .circleci/config.yml should also be updated. version: 1.21 repository: path: github.com/prometheus-community/ipmi_exporter build: flags: -a -tags 'netgo static_build' ldflags: | -X github.com/prometheus/common/version.Version={{.Version}} -X github.com/prometheus/common/version.Revision={{.Revision}} -X github.com/prometheus/common/version.Branch={{.Branch}} -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} tarball: files: - LICENSE crossbuild: platforms: # Disable aix: undefined: syscall.Mkfifo # - aix - darwin - dragonfly - freebsd # Disable illumos: undefined: syscall.Mkfifo # - illumos - linux - netbsd - openbsd # Disable windows: undefined: syscall.Mkfifo # - windows prometheus-ipmi-exporter-1.8.0/.yamllint000066400000000000000000000006631455375552000203670ustar00rootroot00000000000000--- extends: default rules: braces: max-spaces-inside: 1 level: error brackets: max-spaces-inside: 1 level: error commas: disable comments: disable comments-indentation: disable document-start: disable indentation: spaces: consistent indent-sequences: consistent key-duplicates: ignore: | config/testdata/section_key_dup.bad.yml line-length: disable truthy: check-keys: false prometheus-ipmi-exporter-1.8.0/CHANGELOG.md000066400000000000000000000045071455375552000203470ustar00rootroot00000000000000## next ## 1.8.0 / 2024-10-23 * Added BMC watchdog collector (#176) * Added SEL event metrics collector (#179) * Various dependency updates ## 1.7.0 / 2023-10-18 * Update common files * Update build * Update golang to 1.21 * Update dependencies * Switch to Alpine-based Docker image * Add missing error handling * Added chassis cooling fault and drive fault metrics * Now, `ipmi_dcmi_power_consumption_watts` metric is not present if Power Measurement feature is not present. Before this change - the value was zero ## 1.6.1 / 2022-06-17 * Another "I screwed up the release" release ## 1.6.0 / 2022-06-17 * Many improvements in underlying Prometheus libraries * Make sure `ipmimonitoring` outputs the sensor state ## 1.5.2 / 2022-03-14 * Base docker images on debian/bullseye-slim * Update common files ## 1.5.1 / 2022-02-21 * Bugfix release for the release process itself :) ## 1.5.0 / 2022-02-21 * move to prometheus-community * new build system and (hopefully) the docker namespace * some fan sensors that measure in "percent of maximum rotation speed" now show up as fans (previously generic sensors) Thanks a lot to all the contributors and sorry for the long wait! ## 1.4.0 / 2021-06-01 * Includes a lot of refactoring under the hood * Add ability to customize the commands executed by the collectors - see the sample config for some examples. ## 1.3.2 / 2021-02-22 * Fixes in the `bmc` collector for systems which do not support retrieving the system firmware revision (see #57) * Fix for sensors returning multiple events formatted as a string with questionable quoting (see #62) * Use latest go builder container for the Docker image ## 1.3.1 / 2020-10-22 * Fix #57 - that's all :slightly_smiling_face: ## 1.3.0 / 2020-07-26 * New `sm-lan-mode` collector to get the ["LAN mode" setting](https://www.supermicro.com/support/faqs/faq.cfm?faq=28159) on Supermicro BMCs (not enabled by default) * Added "system firmware version" (i.e. the host's BIOS version) to the BMC info metric * Update all dependencies ## 1.2.0 / 2020-04-22 * New `sel` collector to get number of SEL entries and free space * Update all dependencies ## 1.1.0 / 2020-02-14 * Added config option for FreeIPMI workaround-flags * Added missing documentation bits around `ipmi-chassis` usage * Updated dependencies to latest version ## 1.0.0 / 2019-10-18 Initial release. prometheus-ipmi-exporter-1.8.0/CODE_OF_CONDUCT.md000066400000000000000000000002301455375552000213220ustar00rootroot00000000000000# Prometheus Community Code of Conduct Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). prometheus-ipmi-exporter-1.8.0/CONTRIBUTING.md000066400000000000000000000034441455375552000207660ustar00rootroot00000000000000## Contributing to ipmi_exporter In the spirit of [free software][free-sw], **everyone** is encouraged to help improve this project. Here are some ways that *you* can contribute: * Use alpha, beta, and pre-released software versions. * Report bugs. * Suggest new features. * Write or edit documentation. * Write specifications. * Write code; **no patch is too small**: fix typos, add comments, add tests, clean up inconsistent whitespace. * Refactor code. * Fix [issues][]. * Review patches. ## Submitting an issue We use the [GitHub issue tracker][issues] to track bugs and features. Before you submit a bug report or feature request, check to make sure that it has not already been submitted. When you submit a bug report, include a [Gist][] that includes a stack trace and any details that might be necessary to reproduce the bug. ## Submitting a pull request 1. [Fork the repository][fork]. 2. [Create a topic branch][branch]. 3. Implement your feature or bug fix. 4. Unlike we did so far, maybe add tests and make sure that they completely cover your changes and potential edge cases. 5. If there are tests now, run `go test`. If your tests fail, revise your code and tests, and rerun `go test` until they pass. 6. Add documentation for your feature or bug fix in the code, documentation, or PR/commit message. 7. Commit and push your changes. 8. [Submit a pull request][pr] that includes a link to the [issues][] for which you are submitting a bug fix (if applicable). [branch]: http://learn.github.com/p/branching.html [fork]: http://help.github.com/fork-a-repo [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html [gist]: https://gist.github.com [issues]: https://github.com/prometheus-community/ipmi_exporter/issues [pr]: http://help.github.com/send-pull-requests prometheus-ipmi-exporter-1.8.0/Dockerfile000066400000000000000000000005071455375552000205240ustar00rootroot00000000000000ARG ARCH="amd64" ARG OS="linux" FROM alpine:3 RUN apk --no-cache add freeipmi LABEL maintainer="The Prometheus Authors " ARG ARCH="amd64" ARG OS="linux" COPY .build/${OS}-${ARCH}/ipmi_exporter /bin/ipmi_exporter EXPOSE 9290 USER nobody ENTRYPOINT [ "/bin/ipmi_exporter" ] prometheus-ipmi-exporter-1.8.0/LICENSE000066400000000000000000000022041455375552000175330ustar00rootroot00000000000000The MIT License Copyright (c) 2019-2021 SoundCloud Ltd. and the IPMI exporter developers Copyright (c) 2021 The Prometheus Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. prometheus-ipmi-exporter-1.8.0/Makefile000066400000000000000000000014751455375552000201770ustar00rootroot00000000000000# Copyright 2021 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. # Override the default common all. .PHONY: all all: precheck style unused build test #DOCKER_ARCHS ?= amd64 arm64 DOCKER_ARCHS ?= amd64 DOCKER_IMAGE_NAME ?= ipmi-exporter DOCKER_REPO ?= prometheuscommunity include Makefile.common prometheus-ipmi-exporter-1.8.0/Makefile.common000066400000000000000000000216551455375552000214700ustar00rootroot00000000000000# Copyright 2018 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # A common Makefile that includes rules to be reused in different prometheus projects. # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! # Example usage : # Create the main Makefile in the root project directory. # include Makefile.common # customTarget: # @echo ">> Running customTarget" # # Ensure GOBIN is not set during build so that promu is installed to the correct path unexport GOBIN GO ?= go GOFMT ?= $(GO)fmt FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) GOOPTS ?= GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) GO_VERSION ?= $(shell $(GO) version) GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') PROMU := $(FIRST_GOPATH)/bin/promu pkgs = ./... ifeq (arm, $(GOHOSTARCH)) GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) else GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) endif GOTEST := $(GO) test GOTEST_DIR := ifneq ($(CIRCLE_JOB),) ifneq ($(shell command -v gotestsum > /dev/null),) GOTEST_DIR := test-results GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- endif endif PROMU_VERSION ?= 0.15.0 PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_VERSION ?= v1.54.2 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) # If we're in CI and there is an Actions file, that means the linter # is being run in Actions, so we don't need to run it here. ifneq (,$(SKIP_GOLANGCI_LINT)) GOLANGCI_LINT := else ifeq (,$(CIRCLE_JOB)) GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint else ifeq (,$(wildcard .github/workflows/golangci-lint.yml)) GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint endif endif endif PREFIX ?= $(shell pwd) BIN_DIR ?= $(shell pwd) DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) DOCKERFILE_PATH ?= ./Dockerfile DOCKERBUILD_CONTEXT ?= ./ DOCKER_REPO ?= prom DOCKER_ARCHS ?= amd64 BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG)) ifeq ($(GOHOSTARCH),amd64) ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) # Only supported on amd64 test-flags := -race endif endif # This rule is used to forward a target like "build" to "common-build". This # allows a new "build" target to be defined in a Makefile which includes this # one and override "common-build" without override warnings. %: common-% ; .PHONY: common-all common-all: precheck style check_license lint yamllint unused build test .PHONY: common-style common-style: @echo ">> checking code style" @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ if [ -n "$${fmtRes}" ]; then \ echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ echo "Please ensure you are using $$($(GO) version) for formatting code."; \ exit 1; \ fi .PHONY: common-check_license common-check_license: @echo ">> checking license header" @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ done); \ if [ -n "$${licRes}" ]; then \ echo "license header checking failed:"; echo "$${licRes}"; \ exit 1; \ fi .PHONY: common-deps common-deps: @echo ">> getting dependencies" $(GO) mod download .PHONY: update-go-deps update-go-deps: @echo ">> updating Go dependencies" @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ $(GO) get -d $$m; \ done $(GO) mod tidy .PHONY: common-test-short common-test-short: $(GOTEST_DIR) @echo ">> running short tests" $(GOTEST) -short $(GOOPTS) $(pkgs) .PHONY: common-test common-test: $(GOTEST_DIR) @echo ">> running all tests" $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs) $(GOTEST_DIR): @mkdir -p $@ .PHONY: common-format common-format: @echo ">> formatting code" $(GO) fmt $(pkgs) .PHONY: common-vet common-vet: @echo ">> vetting code" $(GO) vet $(GOOPTS) $(pkgs) .PHONY: common-lint common-lint: $(GOLANGCI_LINT) ifdef GOLANGCI_LINT @echo ">> running golangci-lint" # 'go list' needs to be executed before staticcheck to prepopulate the modules cache. # Otherwise staticcheck might fail randomly for some reason not yet explained. $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) endif .PHONY: common-yamllint common-yamllint: @echo ">> running yamllint on all YAML files in the repository" ifeq (, $(shell command -v yamllint > /dev/null)) @echo "yamllint not installed so skipping" else yamllint . endif # For backward-compatibility. .PHONY: common-staticcheck common-staticcheck: lint .PHONY: common-unused common-unused: @echo ">> running check for unused/missing packages in go.mod" $(GO) mod tidy @git diff --exit-code -- go.sum go.mod .PHONY: common-build common-build: promu @echo ">> building binaries" $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) .PHONY: common-tarball common-tarball: promu @echo ">> building release tarball" $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) .PHONY: common-docker $(BUILD_DOCKER_ARCHS) common-docker: $(BUILD_DOCKER_ARCHS) $(BUILD_DOCKER_ARCHS): common-docker-%: docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ -f $(DOCKERFILE_PATH) \ --build-arg ARCH="$*" \ --build-arg OS="linux" \ $(DOCKERBUILD_CONTEXT) .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) common-docker-publish: $(PUBLISH_DOCKER_ARCHS) $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) common-docker-tag-latest: $(TAG_DOCKER_ARCHS) $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" .PHONY: common-docker-manifest common-docker-manifest: DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)) DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" .PHONY: promu promu: $(PROMU) $(PROMU): $(eval PROMU_TMP := $(shell mktemp -d)) curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) mkdir -p $(FIRST_GOPATH)/bin cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu rm -r $(PROMU_TMP) .PHONY: proto proto: @echo ">> generating code from proto files" @./scripts/genproto.sh ifdef GOLANGCI_LINT $(GOLANGCI_LINT): mkdir -p $(FIRST_GOPATH)/bin curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ | sed -e '/install -d/d' \ | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) endif .PHONY: precheck precheck:: define PRECHECK_COMMAND_template = precheck:: $(1)_precheck PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) .PHONY: $(1)_precheck $(1)_precheck: @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ exit 1; \ fi endef prometheus-ipmi-exporter-1.8.0/NOTICE000066400000000000000000000001731455375552000174350ustar00rootroot00000000000000IPMI Exporter Copyright 2019-2021 SoundCloud Ltd. and the IPMI exporter developers Copyright 2021 The Prometheus Authors prometheus-ipmi-exporter-1.8.0/README.md000066400000000000000000000111701455375552000200070ustar00rootroot00000000000000Prometheus IPMI Exporter ======================== [![Build Status](https://circleci.com/gh/prometheus-community/ipmi_exporter.svg?style=svg)](https://circleci.com/gh/prometheus-community/ipmi_exporter) This is an IPMI exporter for [Prometheus][prometheus]. [prometheus]: https://prometheus.io "Prometheus homepage" It supports both the regular `/metrics` endpoint, exposing metrics from the host that the exporter is running on, as well as an `/ipmi` endpoint that supports IPMI over RMCP - one exporter running on one host can be used to monitor a large number of IPMI interfaces by passing the `target` parameter to a scrape. The exporter relies on tools from the [FreeIPMI][freeipmi] suite for the actual IPMI implementation. [freeipmi]: https://www.gnu.org/software/freeipmi/ "FreeIPMI homepage" ## Installation For most use-cases, simply download the [the latest release][releases]. [releases]: https://github.com/prometheus-community/ipmi_exporter/releases "IPMI exporter releases on Github" For Kubernets, you can use the community-maintained [Helm chart][helm]. [helm]: https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus-ipmi-exporter "IPMI exporter Helm chart in the helm-charts Github repo" ### Building from source You need a Go development environment. Then, simply run `make` to build the executable: make This uses the common prometheus tooling to build and run some tests. Alternatively, you can use the standard Go tooling, which will install the executable in `$GOPATH/bin`: go get github.com/prometheus-community/ipmi_exporter ### Building a Docker container You can build a Docker container with the included `docker` make target: make promu promu crossbuild -p linux/amd64 make docker This will not even require Go tooling on the host. See the included [docker compose example](docker-compose.yml) for how to use the resulting container. ### Building a RPM Package See [how to build a RPM package](contrib/rpm/README.md). ## Running A minimal invocation looks like this: ./ipmi_exporter Supported parameters include: - `web.listen-address`: the address/port to listen on (default: `":9290"`) - `config.file`: path to the configuration file (default: none) - `freeipmi.path`: path to the FreeIPMI executables (default: rely on `$PATH`) For syntax and a complete list of available parameters, run: ./ipmi_exporter -h Make sure you have the following tools from the [FreeIPMI][freeipmi] suite installed: - `ipmimonitoring`/`ipmi-sensors` - `ipmi-dcmi` - `ipmi-raw` - `bmc-info` - `ipmi-sel` - `ipmi-chassis` ### Running as unprivileged user If you are running the exporter as unprivileged user, but need to execute the FreeIPMI tools as root, you can do the following: 1. Add sudoers files to permit the following commands ``` ipmi-exporter ALL = NOPASSWD: /usr/sbin/ipmimonitoring,\ /usr/sbin/ipmi-sensors,\ /usr/sbin/ipmi-dcmi,\ /usr/sbin/ipmi-raw,\ /usr/sbin/bmc-info,\ /usr/sbin/ipmi-chassis,\ /usr/sbin/ipmi-sel ``` 2. In your module config, override the collector command with `sudo` for every collector you are using and add the actual command as custom argument. Example for the "ipmi" collector: ```yaml collector_cmd: ipmi: sudo custom_args: ipmi: - "ipmimonitoring" ``` See the last module in the [example config](ipmi_remote.yml). ### Running in Docker **NOTE:** you should only use Docker for remote metrics. See [Building a Docker container](#building-a-docker-container) and the example `docker-compose.yml`. Edit the `ipmi_remote.yml` file to configure IPMI credentials, then run with: sudo docker-compose up -d By default, the server will bind on `0.0.0.0:9290`. ## Configuration The [configuration](docs/configuration.md) document describes both the configuration of the IPMI exporter itself as well as providing some guidance for configuring the Prometheus server to scrape it. ## TLS and basic authentication The IPMI Exporter supports TLS and basic authentication. To use TLS and/or basic authentication, you need to pass a configuration file using the `--web.config.file` parameter. The format of the file is described [in the exporter-toolkit repository][toolkit]. [toolkit]: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md ## Exported data For a description of the metrics that this exporter provides, see the [metrics](docs/metrics.md) document. prometheus-ipmi-exporter-1.8.0/SECURITY.md000066400000000000000000000002541455375552000203220ustar00rootroot00000000000000# Reporting a security issue The Prometheus security policy, including how to report vulnerabilities, can be found here: prometheus-ipmi-exporter-1.8.0/VERSION000066400000000000000000000000061455375552000175740ustar00rootroot000000000000001.8.0 prometheus-ipmi-exporter-1.8.0/collector.go000066400000000000000000000055711455375552000210550ustar00rootroot00000000000000// Copyright 2021 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 ( "path" "time" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( namespace = "ipmi" targetLocal = "" ) type collector interface { Name() CollectorName Cmd() string Args() []string Collect(output freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) } type metaCollector struct { target string module string config *SafeConfig } type ipmiTarget struct { host string config IPMIConfig } var ( upDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "up"), "'1' if a scrape of the IPMI device was successful, '0' otherwise.", []string{"collector"}, nil, ) durationDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "scrape_duration", "seconds"), "Returns how long the scrape took to complete in seconds.", nil, nil, ) ) // Describe implements Prometheus.Collector. func (c metaCollector) Describe(ch chan<- *prometheus.Desc) { // all metrics are described ad-hoc } func markCollectorUp(ch chan<- prometheus.Metric, name string, up int) { ch <- prometheus.MustNewConstMetric( upDesc, prometheus.GaugeValue, float64(up), name, ) } // Collect implements Prometheus.Collector. func (c metaCollector) Collect(ch chan<- prometheus.Metric) { start := time.Now() defer func() { duration := time.Since(start).Seconds() level.Debug(logger).Log("msg", "Scrape duration", "target", targetName(c.target), "duration", duration) ch <- prometheus.MustNewConstMetric( durationDesc, prometheus.GaugeValue, duration, ) }() config := c.config.ConfigForTarget(c.target, c.module) target := ipmiTarget{ host: c.target, config: config, } for _, collector := range config.GetCollectors() { var up int level.Debug(logger).Log("msg", "Running collector", "target", target.host, "collector", collector.Name()) fqcmd := path.Join(*executablesPath, collector.Cmd()) args := collector.Args() cfg := config.GetFreeipmiConfig() result := freeipmi.Execute(fqcmd, args, cfg, target.host, logger) up, _ = collector.Collect(result, ch, target) markCollectorUp(ch, string(collector.Name()), up) } } func targetName(target string) string { if target == targetLocal { return "[local]" } return target } prometheus-ipmi-exporter-1.8.0/collector_bmc.go000066400000000000000000000043531455375552000216730ustar00rootroot00000000000000// Copyright 2021 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 ( "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( BMCCollectorName CollectorName = "bmc" ) var ( bmcInfoDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc", "info"), "Constant metric with value '1' providing details about the BMC.", []string{"firmware_revision", "manufacturer_id", "system_firmware_version"}, nil, ) ) type BMCCollector struct{} func (c BMCCollector) Name() CollectorName { return BMCCollectorName } func (c BMCCollector) Cmd() string { return "bmc-info" } func (c BMCCollector) Args() []string { return []string{} } func (c BMCCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { firmwareRevision, err := freeipmi.GetBMCInfoFirmwareRevision(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC data", "target", targetName(target.host), "error", err) return 0, err } manufacturerID, err := freeipmi.GetBMCInfoManufacturerID(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC data", "target", targetName(target.host), "error", err) return 0, err } systemFirmwareVersion, err := freeipmi.GetBMCInfoSystemFirmwareVersion(result) if err != nil { // This one is not always available. level.Debug(logger).Log("msg", "Failed to parse bmc-info data", "target", targetName(target.host), "error", err) systemFirmwareVersion = "N/A" } ch <- prometheus.MustNewConstMetric( bmcInfoDesc, prometheus.GaugeValue, 1, firmwareRevision, manufacturerID, systemFirmwareVersion, ) return 1, nil } prometheus-ipmi-exporter-1.8.0/collector_bmc_watchdog.go000066400000000000000000000150431455375552000235510ustar00rootroot00000000000000// Copyright 2021 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 ( "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( BMCWatchdogCollectorName CollectorName = "bmc-watchdog" ) var ( bmcWatchdogTimerDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "timer_state"), "Watchdog timer running (1: running, 0: stopped)", []string{}, nil, ) watchdogTimerUses = []string{"BIOS FRB2", "BIOS POST", "OS LOAD", "SMS/OS", "OEM"} bmcWatchdogTimerUseDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "timer_use_state"), "Watchdog timer use (1: active, 0: inactive)", []string{"name"}, nil, ) bmcWatchdogLoggingDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "logging_state"), "Watchdog log flag (1: Enabled, 0: Disabled / note: reverse of freeipmi)", []string{}, nil, ) watchdogTimeoutActions = []string{"None", "Hard Reset", "Power Down", "Power Cycle"} bmcWatchdogTimeoutActionDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "timeout_action_state"), "Watchdog timeout action (1: active, 0: inactive)", []string{"action"}, nil, ) watchdogPretimeoutInterrupts = []string{"None", "SMI", "NMI / Diagnostic Interrupt", "Messaging Interrupt"} bmcWatchdogPretimeoutInterruptDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "pretimeout_interrupt_state"), "Watchdog pre-timeout interrupt (1: active, 0: inactive)", []string{"interrupt"}, nil, ) bmcWatchdogPretimeoutIntervalDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "pretimeout_interval_seconds"), "Watchdog pre-timeout interval in seconds", []string{}, nil, ) bmcWatchdogInitialCountdownDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "initial_countdown_seconds"), "Watchdog initial countdown in seconds", []string{}, nil, ) bmcWatchdogCurrentCountdownDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "current_countdown_seconds"), "Watchdog initial countdown in seconds", []string{}, nil, ) ) type BMCWatchdogCollector struct{} func (c BMCWatchdogCollector) Name() CollectorName { return BMCWatchdogCollectorName } func (c BMCWatchdogCollector) Cmd() string { return "bmc-watchdog" } func (c BMCWatchdogCollector) Args() []string { return []string{"--get"} } func (c BMCWatchdogCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { timerState, err := freeipmi.GetBMCWatchdogTimerState(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC watchdog timer", "target", targetName(target.host), "error", err) return 0, err } currentTimerUse, err := freeipmi.GetBMCWatchdogTimerUse(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC watchdog timer use", "target", targetName(target.host), "error", err) return 0, err } loggingState, err := freeipmi.GetBMCWatchdogLoggingState(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC watchdog logging", "target", targetName(target.host), "error", err) return 0, err } currentTimeoutAction, err := freeipmi.GetBMCWatchdogTimeoutAction(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC watchdog timeout action", "target", targetName(target.host), "error", err) return 0, err } currentPretimeoutInterrupt, err := freeipmi.GetBMCWatchdogPretimeoutInterrupt(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC watchdog pretimeout interrupt", "target", targetName(target.host), "error", err) return 0, err } pretimeoutInterval, err := freeipmi.GetBMCWatchdogPretimeoutInterval(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC watchdog pretimeout interval", "target", targetName(target.host), "error", err) return 0, err } initialCountdown, err := freeipmi.GetBMCWatchdogInitialCountdown(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC watchdog initial countdown", "target", targetName(target.host), "error", err) return 0, err } currentCountdown, err := freeipmi.GetBMCWatchdogCurrentCountdown(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect BMC watchdog current countdown", "target", targetName(target.host), "error", err) return 0, err } ch <- prometheus.MustNewConstMetric(bmcWatchdogTimerDesc, prometheus.GaugeValue, timerState) for _, timerUse := range watchdogTimerUses { if currentTimerUse == timerUse { ch <- prometheus.MustNewConstMetric(bmcWatchdogTimerUseDesc, prometheus.GaugeValue, 1, timerUse) } else { ch <- prometheus.MustNewConstMetric(bmcWatchdogTimerUseDesc, prometheus.GaugeValue, 0, timerUse) } } ch <- prometheus.MustNewConstMetric(bmcWatchdogLoggingDesc, prometheus.GaugeValue, loggingState) for _, timeoutAction := range watchdogTimeoutActions { if currentTimeoutAction == timeoutAction { ch <- prometheus.MustNewConstMetric(bmcWatchdogTimeoutActionDesc, prometheus.GaugeValue, 1, timeoutAction) } else { ch <- prometheus.MustNewConstMetric(bmcWatchdogTimeoutActionDesc, prometheus.GaugeValue, 0, timeoutAction) } } for _, pretimeoutInterrupt := range watchdogPretimeoutInterrupts { if currentPretimeoutInterrupt == pretimeoutInterrupt { ch <- prometheus.MustNewConstMetric(bmcWatchdogPretimeoutInterruptDesc, prometheus.GaugeValue, 1, pretimeoutInterrupt) } else { ch <- prometheus.MustNewConstMetric(bmcWatchdogPretimeoutInterruptDesc, prometheus.GaugeValue, 0, pretimeoutInterrupt) } } ch <- prometheus.MustNewConstMetric(bmcWatchdogPretimeoutIntervalDesc, prometheus.GaugeValue, pretimeoutInterval) ch <- prometheus.MustNewConstMetric(bmcWatchdogInitialCountdownDesc, prometheus.GaugeValue, initialCountdown) ch <- prometheus.MustNewConstMetric(bmcWatchdogCurrentCountdownDesc, prometheus.GaugeValue, currentCountdown) return 1, nil } prometheus-ipmi-exporter-1.8.0/collector_chassis.go000066400000000000000000000054141455375552000225660ustar00rootroot00000000000000// Copyright 2021 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 ( "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( ChassisCollectorName CollectorName = "chassis" ) var ( chassisPowerStateDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "chassis", "power_state"), "Current power state (1=on, 0=off).", []string{}, nil, ) chassisDriveFaultDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "chassis", "drive_fault_state"), "Current drive fault state (1=false, 0=true).", []string{}, nil, ) chassisCoolingFaultDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "chassis", "cooling_fault_state"), "Current Cooling/fan fault state (1=false, 0=true).", []string{}, nil, ) ) type ChassisCollector struct{} func (c ChassisCollector) Name() CollectorName { return ChassisCollectorName } func (c ChassisCollector) Cmd() string { return "ipmi-chassis" } func (c ChassisCollector) Args() []string { return []string{"--get-chassis-status"} } func (c ChassisCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { currentChassisPowerState, err := freeipmi.GetChassisPowerState(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect chassis data", "target", targetName(target.host), "error", err) return 0, err } currentChassisDriveFault, err := freeipmi.GetChassisDriveFault(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect chassis data", "target", targetName(target.host), "error", err) return 0, err } currentChassisCoolingFault, err := freeipmi.GetChassisCoolingFault(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect chassis data", "target", targetName(target.host), "error", err) return 0, err } ch <- prometheus.MustNewConstMetric( chassisPowerStateDesc, prometheus.GaugeValue, currentChassisPowerState, ) ch <- prometheus.MustNewConstMetric( chassisDriveFaultDesc, prometheus.GaugeValue, currentChassisDriveFault, ) ch <- prometheus.MustNewConstMetric( chassisCoolingFaultDesc, prometheus.GaugeValue, currentChassisCoolingFault, ) return 1, nil } prometheus-ipmi-exporter-1.8.0/collector_dcmi.go000066400000000000000000000034641455375552000220500ustar00rootroot00000000000000// Copyright 2021 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 ( "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( DCMICollectorName CollectorName = "dcmi" ) var ( powerConsumptionDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "dcmi", "power_consumption_watts"), "Current power consumption in Watts.", []string{}, nil, ) ) type DCMICollector struct{} func (c DCMICollector) Name() CollectorName { return DCMICollectorName } func (c DCMICollector) Cmd() string { return "ipmi-dcmi" } func (c DCMICollector) Args() []string { return []string{"--get-system-power-statistics"} } func (c DCMICollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { currentPowerConsumption, err := freeipmi.GetCurrentPowerConsumption(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect DCMI data", "target", targetName(target.host), "error", err) return 0, err } // Returned value negative == Power Measurement is not avail if currentPowerConsumption > -1 { ch <- prometheus.MustNewConstMetric( powerConsumptionDesc, prometheus.GaugeValue, currentPowerConsumption, ) } return 1, nil } prometheus-ipmi-exporter-1.8.0/collector_ipmi.go000066400000000000000000000147111455375552000220670ustar00rootroot00000000000000// Copyright 2021 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" "math" "strconv" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( IPMICollectorName CollectorName = "ipmi" ) var ( sensorStateDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sensor", "state"), "Indicates the severity of the state reported by an IPMI sensor (0=nominal, 1=warning, 2=critical).", []string{"id", "name", "type"}, nil, ) sensorValueDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sensor", "value"), "Generic data read from an IPMI sensor of unknown type, relying on labels for context.", []string{"id", "name", "type"}, nil, ) fanSpeedRPMDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "fan_speed", "rpm"), "Fan speed in rotations per minute.", []string{"id", "name"}, nil, ) fanSpeedRatioDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "fan_speed", "ratio"), "Fan speed as a proportion of the maximum speed.", []string{"id", "name"}, nil, ) fanSpeedStateDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "fan_speed", "state"), "Reported state of a fan speed sensor (0=nominal, 1=warning, 2=critical).", []string{"id", "name"}, nil, ) temperatureDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "temperature", "celsius"), "Temperature reading in degree Celsius.", []string{"id", "name"}, nil, ) temperatureStateDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "temperature", "state"), "Reported state of a temperature sensor (0=nominal, 1=warning, 2=critical).", []string{"id", "name"}, nil, ) voltageDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "voltage", "volts"), "Voltage reading in Volts.", []string{"id", "name"}, nil, ) voltageStateDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "voltage", "state"), "Reported state of a voltage sensor (0=nominal, 1=warning, 2=critical).", []string{"id", "name"}, nil, ) currentDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "current", "amperes"), "Current reading in Amperes.", []string{"id", "name"}, nil, ) currentStateDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "current", "state"), "Reported state of a current sensor (0=nominal, 1=warning, 2=critical).", []string{"id", "name"}, nil, ) powerDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "power", "watts"), "Power reading in Watts.", []string{"id", "name"}, nil, ) powerStateDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "power", "state"), "Reported state of a power sensor (0=nominal, 1=warning, 2=critical).", []string{"id", "name"}, nil, ) ) type IPMICollector struct{} func (c IPMICollector) Name() CollectorName { return IPMICollectorName } func (c IPMICollector) Cmd() string { return "ipmimonitoring" } func (c IPMICollector) Args() []string { return []string{ "-Q", "--ignore-unrecognized-events", "--comma-separated-output", "--no-header-output", "--sdr-cache-recreate", "--output-event-bitmask", "--output-sensor-state", } } func (c IPMICollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { excludeIds := target.config.ExcludeSensorIDs targetHost := targetName(target.host) results, err := freeipmi.GetSensorData(result, excludeIds) if err != nil { level.Error(logger).Log("msg", "Failed to collect sensor data", "target", targetHost, "error", err) return 0, err } for _, data := range results { var state float64 switch data.State { case "Nominal": state = 0 case "Warning": state = 1 case "Critical": state = 2 case "N/A": state = math.NaN() default: level.Error(logger).Log("msg", "Unknown sensor state", "target", targetHost, "state", data.State) state = math.NaN() } level.Debug(logger).Log("msg", "Got values", "target", targetHost, "data", fmt.Sprintf("%+v", data)) switch data.Unit { case "RPM": collectTypedSensor(ch, fanSpeedRPMDesc, fanSpeedStateDesc, state, data, 1.0) case "C": collectTypedSensor(ch, temperatureDesc, temperatureStateDesc, state, data, 1.0) case "A": collectTypedSensor(ch, currentDesc, currentStateDesc, state, data, 1.0) case "V": collectTypedSensor(ch, voltageDesc, voltageStateDesc, state, data, 1.0) case "W": collectTypedSensor(ch, powerDesc, powerStateDesc, state, data, 1.0) case "%": switch data.Type { case "Fan": collectTypedSensor(ch, fanSpeedRatioDesc, fanSpeedStateDesc, state, data, 0.01) default: collectGenericSensor(ch, state, data) } default: collectGenericSensor(ch, state, data) } } return 1, nil } func (c IPMICollector) Describe(ch chan<- *prometheus.Desc) { ch <- sensorStateDesc ch <- sensorValueDesc ch <- fanSpeedRPMDesc ch <- fanSpeedRatioDesc ch <- fanSpeedStateDesc ch <- temperatureDesc ch <- temperatureStateDesc ch <- voltageDesc ch <- voltageStateDesc ch <- currentDesc ch <- currentStateDesc ch <- powerDesc ch <- powerStateDesc } func collectTypedSensor(ch chan<- prometheus.Metric, desc, stateDesc *prometheus.Desc, state float64, data freeipmi.SensorData, scale float64) { ch <- prometheus.MustNewConstMetric( desc, prometheus.GaugeValue, data.Value*scale, strconv.FormatInt(data.ID, 10), data.Name, ) ch <- prometheus.MustNewConstMetric( stateDesc, prometheus.GaugeValue, state, strconv.FormatInt(data.ID, 10), data.Name, ) } func collectGenericSensor(ch chan<- prometheus.Metric, state float64, data freeipmi.SensorData) { ch <- prometheus.MustNewConstMetric( sensorValueDesc, prometheus.GaugeValue, data.Value, strconv.FormatInt(data.ID, 10), data.Name, data.Type, ) ch <- prometheus.MustNewConstMetric( sensorStateDesc, prometheus.GaugeValue, state, strconv.FormatInt(data.ID, 10), data.Name, data.Type, ) } prometheus-ipmi-exporter-1.8.0/collector_sel.go000066400000000000000000000041551455375552000217150ustar00rootroot00000000000000// Copyright 2021 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 ( "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( SELCollectorName CollectorName = "sel" ) var ( selEntriesCountDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel", "logs_count"), "Current number of log entries in the SEL.", []string{}, nil, ) selFreeSpaceDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel", "free_space_bytes"), "Current free space remaining for new SEL entries.", []string{}, nil, ) ) type SELCollector struct{} func (c SELCollector) Name() CollectorName { return SELCollectorName } func (c SELCollector) Cmd() string { return "ipmi-sel" } func (c SELCollector) Args() []string { return []string{"--info"} } func (c SELCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { entriesCount, err := freeipmi.GetSELInfoEntriesCount(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect SEL data", "target", targetName(target.host), "error", err) return 0, err } freeSpace, err := freeipmi.GetSELInfoFreeSpace(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect SEL data", "target", targetName(target.host), "error", err) return 0, err } ch <- prometheus.MustNewConstMetric( selEntriesCountDesc, prometheus.GaugeValue, entriesCount, ) ch <- prometheus.MustNewConstMetric( selFreeSpaceDesc, prometheus.GaugeValue, freeSpace, ) return 1, nil } prometheus-ipmi-exporter-1.8.0/collector_sel_events.go000066400000000000000000000076061455375552000233050ustar00rootroot00000000000000// Copyright 2021 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 ( "time" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( SELEventsCollectorName CollectorName = "sel-events" ) var ( selEventsCountByStateDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel_events", "count_by_state"), "Current number of log entries in the SEL by state.", []string{"state"}, nil, ) selEventsCountByNameDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel_events", "count_by_name"), "Current number of custom log entries in the SEL by name.", []string{"name"}, nil, ) selEventsLatestTimestampDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel_events", "latest_timestamp"), "Latest timestamp of custom log entries in the SEL by name.", []string{"name"}, nil, ) ) type SELEventsCollector struct{} func (c SELEventsCollector) Name() CollectorName { return SELEventsCollectorName } func (c SELEventsCollector) Cmd() string { return "ipmi-sel" } func (c SELEventsCollector) Args() []string { return []string{ "-Q", "--comma-separated-output", "--no-header-output", "--sdr-cache-recreate", "--output-event-state", "--interpret-oem-data", "--entity-sensor-names", } } func (c SELEventsCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { selEventConfigs := target.config.SELEvents events, err := freeipmi.GetSELEvents(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect SEL events", "target", targetName(target.host), "error", err) return 0, err } selEventByStateCount := map[string]float64{} selEventByNameCount := map[string]float64{} selEventByNameTimestamp := map[string]float64{} // initialize sel event metrics by zero for _, metricConfig := range selEventConfigs { selEventByNameTimestamp[metricConfig.Name] = 0 selEventByNameCount[metricConfig.Name] = 0 } for _, data := range events { for _, metricConfig := range selEventConfigs { match := metricConfig.Regex.FindStringSubmatch(data.Event) if match != nil { t, err := time.Parse("Jan-02-2006 15:04:05", data.Date+" "+data.Time) if err != nil { level.Error(logger).Log("msg", "Failed to collect SEL event metrics", "target", targetName(target.host), "error", err) return 0, err } newTimestamp := float64(t.Unix()) // save latest timestamp by name metrics if newTimestamp > selEventByNameTimestamp[metricConfig.Name] { selEventByNameTimestamp[metricConfig.Name] = newTimestamp } // save count by name metrics selEventByNameCount[metricConfig.Name]++ } } // save count by state metrics _, ok := selEventByStateCount[data.State] if !ok { selEventByStateCount[data.State] = 0 } selEventByStateCount[data.State]++ } for state, value := range selEventByStateCount { ch <- prometheus.MustNewConstMetric( selEventsCountByStateDesc, prometheus.GaugeValue, value, state, ) } for name, value := range selEventByNameCount { ch <- prometheus.MustNewConstMetric( selEventsCountByNameDesc, prometheus.GaugeValue, value, name, ) ch <- prometheus.MustNewConstMetric( selEventsLatestTimestampDesc, prometheus.GaugeValue, selEventByNameTimestamp[name], name, ) } return 1, nil } prometheus-ipmi-exporter-1.8.0/collector_sm_lan_mode.go000066400000000000000000000043411455375552000234040ustar00rootroot00000000000000// Copyright 2021 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" "strconv" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( SMLANModeCollectorName CollectorName = "sm-lan-mode" ) var ( lanModeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "config", "lan_mode"), "Returns configured LAN mode (0=dedicated, 1=shared, 2=failover).", nil, nil, ) ) type SMLANModeCollector struct{} func (c SMLANModeCollector) Name() CollectorName { return SMLANModeCollectorName } func (c SMLANModeCollector) Cmd() string { return "ipmi-raw" } func (c SMLANModeCollector) Args() []string { return []string{"0x0", "0x30", "0x70", "0x0c", "0"} } func (c SMLANModeCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { octets, err := freeipmi.GetRawOctets(result) if err != nil { level.Error(logger).Log("msg", "Failed to collect LAN mode data", "target", targetName(target.host), "error", err) return 0, err } if len(octets) != 3 { level.Error(logger).Log("msg", "Unexpected number of octets", "target", targetName(target.host), "octets", octets) return 0, fmt.Errorf("unexpected number of octets in raw response: %d", len(octets)) } switch octets[2] { case "00", "01", "02": value, _ := strconv.Atoi(octets[2]) ch <- prometheus.MustNewConstMetric(lanModeDesc, prometheus.GaugeValue, float64(value)) default: level.Error(logger).Log("msg", "Unexpected lan mode status (ipmi-raw)", "target", targetName(target.host), "sgatus", octets[2]) return 0, fmt.Errorf("unexpected lan mode status: %s", octets[2]) } return 1, nil } prometheus-ipmi-exporter-1.8.0/config.go000066400000000000000000000201421455375552000203230ustar00rootroot00000000000000// Copyright 2021 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" "os" "regexp" "strings" "sync" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" yaml "gopkg.in/yaml.v2" ) // CollectorName is used for unmarshaling the list of collectors in the yaml config file type CollectorName string // ConfiguredCollector wraps an existing collector implementation, // potentially altering its default settings. type ConfiguredCollector struct { collector collector command string default_args []string custom_args []string } func (c ConfiguredCollector) Name() CollectorName { return c.collector.Name() } func (c ConfiguredCollector) Cmd() string { if c.command != "" { return c.command } return c.collector.Cmd() } func (c ConfiguredCollector) Args() []string { args := []string{} if c.custom_args != nil { // custom args come first, this way it is quite easy to // override a collector to use e.g. sudo args = append(args, c.custom_args...) } if c.default_args != nil { args = append(args, c.default_args...) } else { args = append(args, c.collector.Args()...) } return args } func (c ConfiguredCollector) Collect(output freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { return c.collector.Collect(output, ch, target) } func (c CollectorName) GetInstance() (collector, error) { // This is where a new collector would have to be "registered" switch c { case IPMICollectorName: return IPMICollector{}, nil case BMCCollectorName: return BMCCollector{}, nil case BMCWatchdogCollectorName: return BMCWatchdogCollector{}, nil case SELCollectorName: return SELCollector{}, nil case SELEventsCollectorName: return SELEventsCollector{}, nil case DCMICollectorName: return DCMICollector{}, nil case ChassisCollectorName: return ChassisCollector{}, nil case SMLANModeCollectorName: return SMLANModeCollector{}, nil } return nil, fmt.Errorf("invalid collector: %s", string(c)) } func (c CollectorName) IsValid() error { _, err := c.GetInstance() return err } // Config is the Go representation of the yaml config file. type Config struct { Modules map[string]IPMIConfig `yaml:"modules"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } // SafeConfig wraps Config for concurrency-safe operations. type SafeConfig struct { sync.RWMutex C *Config } // IPMIConfig is the Go representation of a module configuration in the yaml // config file. type IPMIConfig struct { User string `yaml:"user"` Password string `yaml:"pass"` Privilege string `yaml:"privilege"` Driver string `yaml:"driver"` Timeout uint32 `yaml:"timeout"` Collectors []CollectorName `yaml:"collectors"` ExcludeSensorIDs []int64 `yaml:"exclude_sensor_ids"` WorkaroundFlags []string `yaml:"workaround_flags"` CollectorCmd map[CollectorName]string `yaml:"collector_cmd"` CollectorArgs map[CollectorName][]string `yaml:"default_args"` CustomArgs map[CollectorName][]string `yaml:"custom_args"` SELEvents []*IpmiSELEvent `yaml:"sel_events,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } type IpmiSELEvent struct { Name string `yaml:"name"` RegexRaw string `yaml:"regex"` Regex *regexp.Regexp `yaml:"-"` } var defaultConfig = IPMIConfig{ Collectors: []CollectorName{IPMICollectorName, DCMICollectorName, BMCCollectorName, ChassisCollectorName}, } func checkOverflow(m map[string]interface{}, ctx string) error { if len(m) > 0 { var keys []string for k := range m { keys = append(keys, k) } return fmt.Errorf("unknown fields in %s: %s", ctx, strings.Join(keys, ", ")) } return nil } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (s *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { type plain Config if err := unmarshal((*plain)(s)); err != nil { return err } if err := checkOverflow(s.XXX, "config"); err != nil { return err } return nil } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (s *IPMIConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { *s = defaultConfig type plain IPMIConfig if err := unmarshal((*plain)(s)); err != nil { return err } if err := checkOverflow(s.XXX, "modules"); err != nil { return err } for _, c := range s.Collectors { if err := c.IsValid(); err != nil { return err } } for _, selEvent := range s.SELEvents { selEvent.Regex = regexp.MustCompile(selEvent.RegexRaw) } return nil } func (c IPMIConfig) GetCollectors() []collector { result := []collector{} for _, co := range c.Collectors { // At this point validity has already been checked i, _ := co.GetInstance() cc := ConfiguredCollector{ collector: i, command: c.CollectorCmd[i.Name()], default_args: c.CollectorArgs[i.Name()], custom_args: c.CustomArgs[i.Name()], } result = append(result, cc) } return result } func (c IPMIConfig) GetFreeipmiConfig() string { var b strings.Builder if c.Driver != "" { fmt.Fprintf(&b, "driver-type %s\n", c.Driver) } if c.Privilege != "" { fmt.Fprintf(&b, "privilege-level %s\n", c.Privilege) } if c.User != "" { fmt.Fprintf(&b, "username %s\n", c.User) } if c.Password != "" { fmt.Fprintf(&b, "password %s\n", freeipmi.EscapePassword(c.Password)) } if c.Timeout != 0 { fmt.Fprintf(&b, "session-timeout %d\n", c.Timeout) } if len(c.WorkaroundFlags) > 0 { fmt.Fprintf(&b, "workaround-flags") for _, flag := range c.WorkaroundFlags { fmt.Fprintf(&b, " %s", flag) } fmt.Fprintln(&b) } return b.String() } // ReloadConfig reloads the config in a concurrency-safe way. If the configFile // is unreadable or unparsable, an error is returned and the old config is kept. func (sc *SafeConfig) ReloadConfig(configFile string) error { var c = &Config{} var config []byte var err error if configFile != "" { config, err = os.ReadFile(configFile) if err != nil { level.Error(logger).Log("msg", "Error reading config file", "error", err) return err } } else { config = []byte("# use empty file as default") } if err = yaml.Unmarshal(config, c); err != nil { return err } sc.Lock() sc.C = c sc.Unlock() if configFile != "" { level.Info(logger).Log("msg", "Loaded config file", "path", configFile) } return nil } // HasModule returns true if a given module is configured. It is concurrency-safe. func (sc *SafeConfig) HasModule(module string) bool { sc.Lock() defer sc.Unlock() _, ok := sc.C.Modules[module] return ok } // ConfigForTarget returns the config for a given target/module, or the // default. It is concurrency-safe. func (sc *SafeConfig) ConfigForTarget(target, module string) IPMIConfig { sc.Lock() defer sc.Unlock() var config IPMIConfig var ok = false if module != "default" { config, ok = sc.C.Modules[module] if !ok { level.Error(logger).Log("msg", "Requested module not found, using default", "module", module, "target", targetName(target)) } } // If nothing found, fall back to defaults if !ok { config, ok = sc.C.Modules["default"] if !ok { // This is probably fine for running locally, so not making this a warning level.Debug(logger).Log("msg", "Needed default config for, but none configured, using FreeIPMI defaults", "target", targetName(target)) config = defaultConfig } } return config } prometheus-ipmi-exporter-1.8.0/contrib/000077500000000000000000000000001455375552000201705ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/contrib/rpm/000077500000000000000000000000001455375552000207665ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/contrib/rpm/README.md000066400000000000000000000016001455375552000222420ustar00rootroot00000000000000# Building a RPM Package The RPM package build targets to run the exporter locally as Prometheus user with sudo permissions to expose most metrics. For building a RPM package a build script and [Docker](https://www.docker.com/) build container are available. NOTE: > The build script and the Docker build image must be executed from the project base directory! ## CentOS with rpmbuild A Build script is located in `contrib/rpm/build.sh` to be executed on a CentOS-based host with rpmbuild tool. The RPM package will be available under `$HOME/rpmbuild/`. ## Docker Build Container A Docker build container is provided for CentOS7. ```bash sudo docker build -t centos7_rpmbuild_ipmi_exporter -f contrib/rpm/docker/Dockerfile-centos7 . sudo docker run -v $PWD/contrib/rpm/build:/outdir -it centos7_rpmbuild_ipmi_exporter ``` The RPM package will be available under `contrib/rpm/build/`. prometheus-ipmi-exporter-1.8.0/contrib/rpm/build.sh000077500000000000000000000030111455375552000224170ustar00rootroot00000000000000#!/bin/bash set -e function do_checks { info_msg="The build script must be executed from the projects base directory!" if [ -z "$VERSION" ]; then echo "ERROR: Build failed! VERSION file not found" >&2 echo "INFO: $info_msg" exit 1 fi if [ ! -d "$CONTRIB_DIR" ]; then echo "ERROR: Build failed! Directory does not exist: $CONTRIB_DIR" >&2 echo "INFO: $info_msg" exit 1 fi } export VERSION=$(cat VERSION) export BUILD_DIR=$HOME/rpmbuild export CONTRIB_DIR="contrib/rpm" export PACKAGE_DIR=prometheus-ipmi-exporter-$VERSION do_checks make build mkdir -p $BUILD_DIR/{BUILD,RPMS,SOURCES,SPECS,SRPMS} mkdir -p $BUILD_DIR/SOURCES/$PACKAGE_DIR/usr/bin mkdir -p $BUILD_DIR/SOURCES/$PACKAGE_DIR/usr/lib/systemd/system mkdir -p $BUILD_DIR/SOURCES/$PACKAGE_DIR/etc/sysconfig mkdir -p $BUILD_DIR/SOURCES/$PACKAGE_DIR/etc/sudoers.d sed "s/VERSION/$VERSION/" $CONTRIB_DIR/prometheus-ipmi-exporter.spec > $BUILD_DIR/SPECS/prometheus-ipmi-exporter.spec cp $CONTRIB_DIR/systemd/prometheus-ipmi-exporter.service $BUILD_DIR/SOURCES/$PACKAGE_DIR/usr/lib/systemd/system/ cp $CONTRIB_DIR/sudoers/prometheus-ipmi-exporter $BUILD_DIR/SOURCES/$PACKAGE_DIR/etc/sudoers.d/ cp $CONTRIB_DIR/config/prometheus-ipmi-exporter.yml $BUILD_DIR/SOURCES/$PACKAGE_DIR/etc/sysconfig/ cp ipmi_exporter $BUILD_DIR/SOURCES/$PACKAGE_DIR/usr/bin/ cd $BUILD_DIR/SOURCES tar -czvf $PACKAGE_DIR.tar.gz $PACKAGE_DIR cd $BUILD_DIR echo Build dir is: $BUILD_DIR ls -la $BUILD_DIR/SOURCES rpmbuild -ba $BUILD_DIR/SPECS/prometheus-ipmi-exporter.spec prometheus-ipmi-exporter-1.8.0/contrib/rpm/config/000077500000000000000000000000001455375552000222335ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/contrib/rpm/config/prometheus-ipmi-exporter.yml000066400000000000000000000010411455375552000277470ustar00rootroot00000000000000# Configuration file for ipmi_exporter modules: default: collectors: - bmc - ipmi - dcmi - chassis - sel collector_cmd: bmc: sudo ipmi: sudo dcmi: sudo chassis: sudo sel: sudo custom_args: bmc: - "bmc-info" ipmi: - "ipmimonitoring" dcmi: - "ipmi-dcmi" chassis: - "ipmi-chassis" sel: - "ipmi-sel" prometheus-ipmi-exporter-1.8.0/contrib/rpm/docker/000077500000000000000000000000001455375552000222355ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/contrib/rpm/docker/Dockerfile-centos7000066400000000000000000000007511455375552000256120ustar00rootroot00000000000000FROM centos:7 MAINTAINER jknedlik , Gabriele Iannetti WORKDIR /tmp RUN yum install -y git make gcc rpm-build which RUN curl -LO https://go.dev/dl/go1.18.1.linux-amd64.tar.gz RUN tar -C /usr/local -xvzf go1.18.1.linux-amd64.tar.gz ENV PATH=$PATH:/usr/local/go/bin COPY . /go/ipmi_exporter WORKDIR /go/ipmi_exporter RUN contrib/rpm/build.sh RUN mkdir /outdir ENTRYPOINT ["/bin/sh"] ENTRYPOINT ["cp"] CMD ["-r", "/root/rpmbuild/RPMS/x86_64/", "/outdir"] prometheus-ipmi-exporter-1.8.0/contrib/rpm/prometheus-ipmi-exporter.spec000066400000000000000000000030561455375552000266430ustar00rootroot00000000000000%define __spec_install_post %{nil} %define debug_package %{nil} %define __os_install_post %{_dbpath}/brp-compress Name: prometheus-ipmi-exporter Version: VERSION Release: 1.0%{?dist} Summary: Remote IPMI exporter for Prometheus Group: Monitoring License: The MIT License URL: https://github.com/prometheus-community/ipmi_exporter Source0: %{name}-%{version}.tar.gz Requires(pre): shadow-utils Requires(post): systemd Requires(preun): systemd Requires(postun): systemd %{?systemd_requires} BuildRequires: systemd BuildRoot: %{_tmppath}/%{name}-%{version}-1-root %description Remote IPMI exporter for Prometheus %prep %setup -q %build # Empty section. %install rm -rf %{buildroot} mkdir -p %{buildroot}%{_unitdir}/ cp usr/lib/systemd/system/%{name}.service %{buildroot}%{_unitdir}/ # in builddir cp -a * %{buildroot} %clean rm -rf %{buildroot} %pre getent group prometheus >/dev/null || groupadd -r prometheus getent passwd prometheus >/dev/null || \ useradd -r -g prometheus -d /dev/null -s /sbin/nologin \ -c "Prometheus exporter user" prometheus cp etc/sudoers.d/%{name} /etc/sudoers.d/%{name} exit 0 %post systemctl enable %{name}.service systemctl start %{name}.service %preun %systemd_preun %{name}.service %postun %systemd_postun_with_restart %{name}.service %files %defattr(-,root,root,-) %config /etc/sysconfig/prometheus-ipmi-exporter.yml %attr(0440, root, root) /etc/sudoers.d/prometheus-ipmi-exporter %{_bindir}/ipmi_exporter %{_unitdir}/%{name}.service prometheus-ipmi-exporter-1.8.0/contrib/rpm/sudoers/000077500000000000000000000000001455375552000224525ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/contrib/rpm/sudoers/prometheus-ipmi-exporter000066400000000000000000000007341455375552000273760ustar00rootroot00000000000000# !log_allowed setting should be used instead, but is only supported by version 1.8.29 or higher. Defaults:prometheus !syslog prometheus ALL = NOPASSWD: /usr/sbin/ipmimonitoring,\ /usr/sbin/ipmi-sensors,\ /usr/sbin/ipmi-dcmi,\ /usr/sbin/ipmi-raw,\ /usr/sbin/bmc-info,\ /usr/sbin/ipmi-chassis,\ /usr/sbin/ipmi-sel prometheus-ipmi-exporter-1.8.0/contrib/rpm/systemd/000077500000000000000000000000001455375552000224565ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/contrib/rpm/systemd/prometheus-ipmi-exporter.service000066400000000000000000000004341455375552000310360ustar00rootroot00000000000000[Unit] Description=Prometheus IPMI Exporter Documentation=https://github.com/prometheus-community/ipmi_exporter [Service] ExecStart=/usr/bin/ipmi_exporter --config.file=/etc/sysconfig/prometheus-ipmi-exporter.yml User=prometheus Restart=always [Install] WantedBy=multi-user.target prometheus-ipmi-exporter-1.8.0/docker-compose.yml000066400000000000000000000005751455375552000221740ustar00rootroot00000000000000version: '3.7' services: ipmi_exporter: build: context: . command: --config.file /config.yml volumes: - ./ipmi_remote.yml:/config.yml:ro # replace with your own config ports: - 9290:9290 # bind on 0.0.0.0 # - 127.0.0.1:9290:9290 # or bind to specific interface hostname: ipmi_exporter_docker prometheus-ipmi-exporter-1.8.0/docs/000077500000000000000000000000001455375552000174605ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/docs/configuration.md000066400000000000000000000114111455375552000226470ustar00rootroot00000000000000# Configuration Simply scraping the standard `/metrics` endpoint will make the exporter emit local IPMI metrics. No special configuration is required. For remote metrics, the general configuration pattern is similar to that of the [blackbox exporter](https://github.com/prometheus/blackbox_exporter), i.e. Prometheus scrapes a small number (possibly one) of IPMI exporters with a `target` and `module` URL parameter to tell the exporter which IPMI device it should use to retrieve the IPMI metrics. We offer this approach as IPMI devices often provide useful information even while the supervised host is turned off. If you are running the exporter on a separate host anyway, it makes more sense to have only a few of them, each probing many (possibly thousands of) IPMI devices, rather than one exporter per IPMI device. **NOTE:** If you are using remote metrics, but still want to get the local process metrics from the instance, you must use a `default` module with an empty collectors list and use other modules for the remote hosts. ## IPMI exporter The exporter can read a configuration file by setting `config.file` (see above). To collect local metrics, you might not even need one. For remote metrics, it must contain at least user names and passwords for IPMI access to all targets to be scraped. You can additionally specify the IPMI driver type and privilege level to use (see `man 5 freeipmi.conf` for more details and possible values). The config file supports the notion of "modules", so that different configurations can be re-used for groups of targets. See the section below on how to set the module parameter in Prometheus. The special module "default" is used in case the scrape does not request a specific module. The configuration file also supports a blacklist of sensors, useful in case of OEM-specific sensors that FreeIPMI cannot deal with properly or otherwise misbehaving sensors. This applies to both local and remote metrics. There are two commented example configuration files, see `ipmi_local.yml` for scraping local host metrics and `ipmi_remote.yml` for scraping remote IPMI interfaces. ## Prometheus ### Local metrics Collecting local IPMI metrics is fairly straightforward. Simply configure your server to scrape the default metrics endpoint on the hosts running the exporter. ``` - job_name: ipmi scrape_interval: 1m scrape_timeout: 30s metrics_path: /metrics scheme: http static_configs: - targets: - 10.1.2.23:9290 - 10.1.2.24:9290 - 10.1.2.25:9290 ``` ### Remote metrics To add your IPMI targets to Prometheus, you can use any of the supported service discovery mechanism of your choice. The following example uses the file-based SD and should be easy to adjust to other scenarios. Create a YAML file that contains a list of targets, e.g.: ``` --- - targets: - 10.1.2.23 - 10.1.2.24 - 10.1.2.25 - 10.1.2.26 - 10.1.2.27 - 10.1.2.28 - 10.1.2.29 - 10.1.2.30 labels: job: ipmi_exporter ``` This file needs to be stored on the Prometheus server host. Assuming that this file is called `/srv/ipmi_exporter/targets.yml`, and the IPMI exporter is running on a host that has the DNS name `ipmi-exporter.internal.example.com`, add the following to your Prometheus config: ``` - job_name: ipmi params: module: ['default'] scrape_interval: 1m scrape_timeout: 30s metrics_path: /ipmi scheme: http file_sd_configs: - files: - /srv/ipmi_exporter/targets.yml refresh_interval: 5m relabel_configs: - source_labels: [__address__] separator: ; regex: (.*) target_label: __param_target replacement: ${1} action: replace - source_labels: [__param_target] separator: ; regex: (.*) target_label: instance replacement: ${1} action: replace - separator: ; regex: .* target_label: __address__ replacement: ipmi-exporter.internal.example.com:9290 action: replace ``` This assumes that all hosts use the default module. If you are using modules in the config file, like in the provided `ipmi_remote.yml` example config, you will need to specify on job for each module, using the respective group of targets. In a more extreme case, for example if you are using different passwords on every host, a good approach is to generate an exporter config file that uses the target name as module names, which would allow you to have single job that uses label replace to set the module. Leave out the `params` in the job definition and instead add a relabel rule like this one: ``` - source_labels: [__address__] separator: ; regex: (.*) target_label: __param_module replacement: ${1} action: replace ``` For more information, e.g. how to use mechanisms other than a file to discover the list of hosts to scrape, please refer to the [Prometheus documentation](https://prometheus.io/docs). prometheus-ipmi-exporter-1.8.0/docs/metrics.md000066400000000000000000000233731455375552000214600ustar00rootroot00000000000000# Exported metrics ## Scrape meta data These metrics provide data about the scrape itself: - `ipmi_up{collector=""}` is `1` if the data for this collector could successfully be retrieved from the remote host, `0` otherwise. The following collectors are available and can be enabled or disabled in the config: - `ipmi`: collects IPMI sensor data. If it fails, sensor metrics (see below) will not be available - `dcmi`: collects DCMI data, currently only power consumption. If it fails, power consumption metrics (see below) will not be available - `bmc`: collects BMC details. If it fails, BMC info metrics (see below) will not be available - `bmc-watchdog`: collects status of the watchdog. If it fails, BMC watchdog metrics (see below) will not be available - `chassis`: collects the current chassis power state (on/off). If it fails, the chassis power state metric (see below) will not be available - `sel`: collects system event log (SEL) details. If it fails, SEL metrics (see below) will not be available - `sel-events`: collects metrics for user-defined events in system event log (SEL). If it fails, SEL entries metrics (see below) will not be available - `sm-lan-mode`: collects the "LAN mode" setting in the current BMC config. If it fails, the LAN mode metric (see below) will not be available - `ipmi_scrape_duration_seconds` is the amount of time it took to retrieve the data ## BMC info This metric is only provided if the `bmc` collector is enabled. For some basic information, there is a constant metric `ipmi_bmc_info` with value `1` and labels providing the firmware revision and manufacturer as returned from the BMC, and the host system's firmware version (usually the BIOS version). Example: ipmi_bmc_info{firmware_revision="1.66",manufacturer_id="Dell Inc. (674)",system_firmware_version="2.6.1"} 1 **Note:** some systems do not expose the system's firmware version, in which case it will be exported as `"N/A"`. ## BMC Watchdog These metrics are only provided if the `bmc-watchdog` collector is enabled. The metric `ipmi_bmc_watchdog_timer_state` shows whether the watchdog timer is currently running (1) or stopped (0). The metric `ipmi_bmc_watchdog_timer_use_state` shows which timer use is currently active. Per freeipmi bmc-watchdog manual there are 5 uses. This metric will return 1 for only one of those and 0 for the rest. ipmi_bmc_watchdog_timer_use_state{name="BIOS FRB2"} 1 ipmi_bmc_watchdog_timer_use_state{name="BIOS POST"} 0 ipmi_bmc_watchdog_timer_use_state{name="OEM"} 0 ipmi_bmc_watchdog_timer_use_state{name="OS LOAD"} 0 ipmi_bmc_watchdog_timer_use_state{name="SMS/OS"} 0 The metric `ipmi_bmc_watchdog_logging_state` shows whether the watchdog logging is enabled (1) or not (0). (Note: This is reversed in freeipmi where 0 enables logging and 1 disables it) The metric `ipmi_bmc_watchdog_timeout_action_state` shows whether watchdog will take an action on timeout, and if so which one. Per freeipmi bmc-watchdog manual there are 3 actions. If no action is configured it will be reported as `None`. ipmi_bmc_watchdog_timeout_action_state{action="Hard Reset"} 0 ipmi_bmc_watchdog_timeout_action_state{action="None"} 0 ipmi_bmc_watchdog_timeout_action_state{action="Power Cycle"} 1 ipmi_bmc_watchdog_timeout_action_state{action="Power Down"} 0 The metric `ipmi_bmc_watchdog_timeout_action_state` shows whether a pre-timeout interrupt is currently active and if so, which one. Per freeipmi bmc-watchdog manual there are 3 interrupts. If no interrupt is configured it will be reported as `None`. ipmi_bmc_watchdog_pretimeout_interrupt_state{interrupt="Messaging Interrupt"} 0 ipmi_bmc_watchdog_pretimeout_interrupt_state{interrupt="NMI / Diagnostic Interrupt"} 0 ipmi_bmc_watchdog_pretimeout_interrupt_state{interrupt="None"} 1 ipmi_bmc_watchdog_pretimeout_interrupt_state{interrupt="SMI"} 0 The metric `ipmi_bmc_watchdog_pretimeout_interval_seconds` shows the current pre-timeout interval as measured in seconds. The metric `ipmi_bmc_watchdog_initial_countdown_seconds` shows the configured countdown in seconds. The metric `ipmi_bmc_watchdog_current_countdown_seconds` shows the current countdown in seconds. ## Chassis Power State This metric is only provided if the `chassis` collector is enabled. The metric `ipmi_chassis_power_state` shows the current chassis power state of the machine. The value is 1 for power on, and 0 otherwise. ## Power consumption This metric is only provided if the `dcmi` collector is enabled. The metric `ipmi_dcmi_power_consumption_current_watts` can be used to monitor the live power consumption of the machine in Watts. If in doubt, this metric should be used over any of the sensor data (see below), even if their name might suggest that they measure the same thing. This metric has no labels. ## System event log (SEL) info These metrics are only provided if the `sel` collector is enabled (it isn't by default). The metric `ipmi_sel_entries_count` contains the current number of entries in the SEL. It is a gauge, as the SEL can be cleared at any time. This metric has no labels. The metric `ipmi_sel_free_space_bytes` contains the current number of free space for new SEL entries, in bytes. This metric has no labels. ## System event log (SEL) entries metrics These metrics are only provided if the `sel-events` collector is enabled (it isn't by default). For each event specified in the configuration file (`sel_events` field), will be generated metrics containing the number of such events and the timestamp of their last occurrence. Example: ipmi_sel_events_count_by_name{name="my_custom_event_from_config"} 77 ipmi_sel_events_latest_timestamp{name="my_custom_event_from_config"} 1.703613275e+09 also next aggregated metrics will be exported: ipmi_sel_events_count_by_state{state="Nominal"} 10 ipmi_sel_events_count_by_state{state="Warning"} 5 ## Supermicro LAN mode setting This metric is only provided if the `sm-lan-mode` collector is enabled (it isn't by default). **NOTE:** This is a vendor-specific collector, it will only work on Supermicro hardware, possibly even only on _some_ Supermicro systems. **NOTE:** Retrieving this setting requires setting `privilege: "admin"` in the config. See e.g. https://www.supermicro.com/support/faqs/faq.cfm?faq=28159 The metric `ipmi_config_lan_mode` contains the value for the current "LAN mode" setting (see link above): `0` for "dedicated", `1` for "shared", and `2` for "failover". ## Sensors These metrics are only provided if the `ipmi` collector is enabled. IPMI sensors in general have one or two distinct pieces of information that are of interest: a value and/or a state. The exporter always exports both, even if the value is NaN or the state non-sensical. This is so one can still always find the metrics to avoid ending up in a situation where one is looking for e.g. the value of a sensor that is in a critical state, but can't find it and assume this to be a problem. The state of a sensor can be one of _nominal_, _warning_, _critical_, or _N/A_, reflected by the metric values `0`, `1`, `2`, and `NaN` respectively. Think of this as a kind of severity. For sensors with known semantics (i.e. units), corresponding specific metrics are exported. For everything else, generic metrics are exported. ### Temperature sensors Temperature sensors measure a temperature in degrees Celsius and their state usually reflects the temperature going above the vendor-recommended value. For each temperature sensor, two metrics are exported (state and value), using the sensor ID and the sensor name as labels. Example: ipmi_temperature_celsius{id="18",name="Inlet Temp"} 24 ipmi_temperature_state{id="18",name="Inlet Temp"} 0 ### Fan speed sensors Fan speed sensors measure fan speed in rotations per minute (RPM) or as a percentage of the maximum speed, and their state usually reflects the speed being to low, indicating the fan might be broken. For each fan speed sensor, two metrics are exported (state and value), using the sensor ID and the sensor name as labels. Example: ipmi_fan_speed_rpm{id="12",name="Fan1A"} 4560 ipmi_fan_speed_state{id="12",name="Fan1A"} 0 or, for a percentage based fan: ipmi_fan_speed_ratio{id="58",name="Fan 1 DutyCycle"} 0.2195 ipmi_fan_speed_state{id="58",name="Fan 1 DutyCycle"} 0 ### Voltage sensors Voltage sensors measure a voltage in Volts. For each voltage sensor, two metrics are exported (state and value), using the sensor ID and the sensor name as labels. Example: ipmi_voltage_state{id="2416",name="12V"} 0 ipmi_voltage_volts{id="2416",name="12V"} 12 ### Current sensors Current sensors measure a current in Amperes. For each current sensor, two metrics are exported (state and value), using the sensor ID and the sensor name as labels. Example: ipmi_current_state{id="83",name="Current 1"} 0 ipmi_current_amperes{id="83",name="Current 1"} 0 ### Power sensors Power sensors measure power in Watts. For each power sensor, two metrics are exported (state and value), using the sensor ID and the sensor name as labels. Example: ipmi_power_state{id="90",name="Pwr Consumption"} 0 ipmi_power_watts{id="90",name="Pwr Consumption"} 70 Note that based on our observations, this may or may not be a reading reflecting the actual live power consumption. We recommend using the more explicit [power consumption metrics](#power_consumption) for this. ### Generic sensors For all sensors that can not be classified, two generic metrics are exported, the state and the value. However, to provide a little more context, the sensor type is added as label (in addition to name and ID). Example: ipmi_sensor_state{id="139",name="Power Cable",type="Cable/Interconnect"} 0 ipmi_sensor_value{id="139",name="Power Cable",type="Cable/Interconnect"} NaN prometheus-ipmi-exporter-1.8.0/freeipmi/000077500000000000000000000000001455375552000203305ustar00rootroot00000000000000prometheus-ipmi-exporter-1.8.0/freeipmi/freeipmi.go000066400000000000000000000335251455375552000224670ustar00rootroot00000000000000// Copyright 2021 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 freeipmi import ( "bufio" "bytes" "crypto/rand" "encoding/csv" "encoding/hex" "fmt" "math" "os" "os/exec" "path/filepath" "regexp" "strconv" "strings" "syscall" "github.com/go-kit/log" "github.com/go-kit/log/level" ) var ( ipmiDCMIPowerMeasurementRegex = regexp.MustCompile(`^Power Measurement\s*:\s*(?PActive|Not\sAvailable).*`) ipmiDCMICurrentPowerRegex = regexp.MustCompile(`^Current Power\s*:\s*(?P[0-9.]*)\s*Watts.*`) ipmiChassisPowerRegex = regexp.MustCompile(`^System Power\s*:\s(?P.*)`) ipmiChassisDriveFaultRegex = regexp.MustCompile(`^Drive Fault\s*:\s(?P.*)`) ipmiChassisCoolingFaultRegex = regexp.MustCompile(`^Cooling/fan fault\s*:\s(?P.*)`) ipmiSELEntriesRegex = regexp.MustCompile(`^Number of log entries\s*:\s(?P[0-9.]*)`) ipmiSELFreeSpaceRegex = regexp.MustCompile(`^Free space remaining\s*:\s(?P[0-9.]*)\s*bytes.*`) ipmiSELEventRegex = regexp.MustCompile(`^(?P[0-9]+),\s*(?P[^,]*),(?P