pax_global_header00006660000000000000000000000064147531751400014521gustar00rootroot0000000000000052 comment=c0bb00f70e5fcef0e8414ef8a29cddc4c4db48a3 prometheus-ipmi-exporter-1.10.0/000077500000000000000000000000001475317514000165755ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/.circleci/000077500000000000000000000000001475317514000204305ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/.circleci/config.yml000066400000000000000000000023541475317514000224240ustar00rootroot00000000000000--- 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.23 jobs: test: executor: golang steps: - prometheus/setup_environment - run: make - prometheus/store_artifact: file: ipmi_exporter workflows: version: 2 ipmi_exporter: jobs: - test: filters: tags: only: /.*/ - prometheus/build: name: build 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.10.0/.github/000077500000000000000000000000001475317514000201355ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/.github/dependabot.yml000066400000000000000000000001561475317514000227670ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "monthly" prometheus-ipmi-exporter-1.10.0/.github/workflows/000077500000000000000000000000001475317514000221725ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/.github/workflows/container_description.yml000066400000000000000000000044671475317514000273150ustar00rootroot00000000000000--- name: Push README to Docker Hub on: push: paths: - "README.md" - "README-containers.md" - ".github/workflows/container_description.yml" branches: [ main, master ] permissions: contents: read jobs: PushDockerHubReadme: runs-on: ubuntu-latest name: Push README to Docker Hub if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set docker hub repo name run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV - name: Push README to Dockerhub uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 env: DOCKER_USER: ${{ secrets.DOCKER_HUB_LOGIN }} DOCKER_PASS: ${{ secrets.DOCKER_HUB_PASSWORD }} with: destination_container_repo: ${{ env.DOCKER_REPO_NAME }} provider: dockerhub short_description: ${{ env.DOCKER_REPO_NAME }} # Empty string results in README-containers.md being pushed if it # exists. Otherwise, README.md is pushed. readme_file: '' PushQuayIoReadme: runs-on: ubuntu-latest name: Push README to quay.io if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set quay.io org name run: echo "DOCKER_REPO=$(echo quay.io/${GITHUB_REPOSITORY_OWNER} | tr -d '-')" >> $GITHUB_ENV - name: Set quay.io repo name run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV - name: Push README to quay.io uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 env: DOCKER_APIKEY: ${{ secrets.QUAY_IO_API_TOKEN }} with: destination_container_repo: ${{ env.DOCKER_REPO_NAME }} provider: quay # Empty string results in README-containers.md being pushed if it # exists. Otherwise, README.md is pushed. readme_file: '' prometheus-ipmi-exporter-1.10.0/.github/workflows/golangci-lint.yml000066400000000000000000000023451475317514000254500ustar00rootroot00000000000000--- # 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: permissions: # added using https://github.com/step-security/secure-repo contents: read jobs: golangci: permissions: contents: read # for actions/checkout to fetch code pull-requests: read # for golangci/golangci-lint-action to fetch pull requests name: lint runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install Go uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version: 1.23.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@ec5d18412c0aeab7936cb16880d708ba2a64e1ae # v6.2.0 with: args: --verbose version: v1.63.4 prometheus-ipmi-exporter-1.10.0/.gitignore000066400000000000000000000000361475317514000205640ustar00rootroot00000000000000build/ ipmi_exporter *.tar.gz prometheus-ipmi-exporter-1.10.0/.golangci.yml000066400000000000000000000002131475317514000211550ustar00rootroot00000000000000--- linters: enable: - misspell - revive - sloglint issues: exclude-rules: - path: _test.go linters: - errcheck prometheus-ipmi-exporter-1.10.0/.promu.yml000066400000000000000000000017541475317514000205470ustar00rootroot00000000000000go: # Whenever the Go version is updated here, # .circleci/config.yml should also be updated. version: 1.23 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.10.0/.yamllint000066400000000000000000000007171475317514000204340ustar00rootroot00000000000000--- extends: default ignore: | **/node_modules 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.10.0/CHANGELOG.md000066400000000000000000000054011475317514000204060ustar00rootroot00000000000000## next ## 1.10.0 / 2025-02-12 * Adopt slog, drop go-kit/log (#210) * Add bmc url (#219) * Use long option for quiet cache (#224) * Support for Go-native IPMI (#237) * Add version collector metric (#240) * Various dependency updates and CI improvements ## 1.9.0 / 2024-10-17 * Bring back aarch64 builds (#186) * Ignore time parse error in SEL events (#198) * Don't prepend to already absolute path from config (#199) * Various dependency updates ## 1.8.0 / 2024-01-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.10.0/CODE_OF_CONDUCT.md000066400000000000000000000002301475317514000213670ustar00rootroot00000000000000# 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.10.0/CONTRIBUTING.md000066400000000000000000000034441475317514000210330ustar00rootroot00000000000000## 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.10.0/Dockerfile000066400000000000000000000005401475317514000205660ustar00rootroot00000000000000ARG ARCH="amd64" ARG OS="linux" FROM --platform=${OS}/${ARCH} 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.10.0/LICENSE000066400000000000000000000022041475317514000176000ustar00rootroot00000000000000The 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.10.0/MAINTAINERS.md000066400000000000000000000002001475317514000206610ustar00rootroot00000000000000# Maintainers General maintainers: * Conrad Hoffmann (ch@bitfehler.net / @bitfehler) * Ben Kochie (superq@gmail.com / @SuperQ) prometheus-ipmi-exporter-1.10.0/Makefile000066400000000000000000000014411475317514000202350ustar00rootroot00000000000000# 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_IMAGE_NAME ?= ipmi-exporter DOCKER_REPO ?= prometheuscommunity include Makefile.common prometheus-ipmi-exporter-1.10.0/Makefile.common000066400000000000000000000221631475317514000215300ustar00rootroot00000000000000# 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 2> /dev/null),) GOTEST_DIR := test-results GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- endif endif PROMU_VERSION ?= 0.17.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.63.4 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64)) # 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" $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) endif .PHONY: common-lint-fix common-lint-fix: $(GOLANGCI_LINT) ifdef GOLANGCI_LINT @echo ">> running golangci-lint fix" $(GOLANGCI_LINT) run --fix $(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 2> /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-repo-name common-docker-repo-name: @echo "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)" .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 govulncheck: install-govulncheck govulncheck ./... install-govulncheck: command -v govulncheck > /dev/null || go install golang.org/x/vuln/cmd/govulncheck@latest prometheus-ipmi-exporter-1.10.0/NOTICE000066400000000000000000000001731475317514000175020ustar00rootroot00000000000000IPMI Exporter Copyright 2019-2021 SoundCloud Ltd. and the IPMI exporter developers Copyright 2021 The Prometheus Authors prometheus-ipmi-exporter-1.10.0/README.md000066400000000000000000000102501475317514000200520ustar00rootroot00000000000000Prometheus 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, implementing the multi-target exporter pattern. If you plan to use the latter, please read the guide [Understanding and using the multi-target exporter pattern][multi-target] to get the general idea about the configuration. [multi-target]: https://prometheus.io/docs/guides/multi-target-exporter/ By default, the exporter relies on tools from the [FreeIPMI][freeipmi] suite for the actual IPMI implementation. [freeipmi]: https://www.gnu.org/software/freeipmi/ "FreeIPMI homepage" There is, however, experimental support for using the Go-native [go-ipmi library](https://github.com/bougou/go-ipmi/) instead of FreeIPMI. Feedback to help mature this support would be greatly appreciated. Please read the [native IPMI documentation](docs/native.md) if you are interested. ## 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" Pre-built container images are available on [dockerhub][dockerhub] and [quay.io][quay.io]. [dockerhub]: https://hub.docker.com/r/prometheuscommunity/ipmi-exporter [quay.io]: https://quay.io/repository/prometheuscommunity/ipmi-exporter ### 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 install github.com/prometheus-community/ipmi_exporter@latest ### Building a container image You can build a container image with the included `docker` make target: make promu promu crossbuild -p linux/amd64 -p linux/arm64 make docker ## 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` When running a container image, make sure to: - set `config.file` to where the config file is mounted - expose the default port (9290) or set `web.listen-address` accordingly **NOTE:** you should only use containers for collecting remote metrics. ## 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. ## Privileges Collecting host-local IPMI metrics requires root privileges. See [privileges](docs/privileges.md) document for how to avoid running the exporter as root. prometheus-ipmi-exporter-1.10.0/SECURITY.md000066400000000000000000000002541475317514000203670ustar00rootroot00000000000000# Reporting a security issue The Prometheus security policy, including how to report vulnerabilities, can be found here: prometheus-ipmi-exporter-1.10.0/VERSION000066400000000000000000000000071475317514000176420ustar00rootroot000000000000001.10.0 prometheus-ipmi-exporter-1.10.0/collector.go000066400000000000000000000107031475317514000211130ustar00rootroot00000000000000// 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 ( "context" "path" "strings" "time" "github.com/bougou/go-ipmi" "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(_ 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() logger.Debug("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 logger.Debug("Running collector", "target", target.host, "collector", collector.Name()) fqcmd := collector.Cmd() result := freeipmi.Result{} // Go-native collectors return empty string as command if fqcmd != "" { if !path.IsAbs(fqcmd) { fqcmd = path.Join(*executablesPath, collector.Cmd()) } args := collector.Args() cfg := config.GetFreeipmiConfig() result = freeipmi.Execute(fqcmd, args, cfg, target.host, logger) } up, err := collector.Collect(result, ch, target) if err != nil { logger.Error("Collector failed", "name", collector.Name(), "error", err) } markCollectorUp(ch, string(collector.Name()), up) } } func targetName(target string) string { if target == targetLocal { return "[local]" } return target } func NewNativeClient(target ipmiTarget) (*ipmi.Client, error) { var client *ipmi.Client var err error if target.host == targetLocal { client, err = ipmi.NewOpenClient() } else { client, err = ipmi.NewClient(target.host, 623, target.config.User, target.config.Password) } if err != nil { logger.Error("Error creating IPMI client", "target", target.host, "error", err) return nil, err } if target.host != targetLocal { // TODO it's probably safe to ditch other interfaces? client = client.WithInterface(ipmi.InterfaceLanplus) } if target.config.Timeout != 0 { client = client.WithTimeout(time.Duration(target.config.Timeout * uint32(time.Millisecond))) } if target.config.Privilege != "" { // TODO this means different default (unspecified) for native vs. FreeIPMI (operator) priv := ipmi.PrivilegeLevelUnspecified switch strings.ToLower(target.config.Privilege) { case "admin": priv = ipmi.PrivilegeLevelAdministrator case "operator": priv = ipmi.PrivilegeLevelOperator case "user": priv = ipmi.PrivilegeLevelUser } client = client.WithMaxPrivilegeLevel(priv) } // TODO workaround-flags not used in native client if err := client.Connect(context.TODO()); err != nil { logger.Error("Error connecting to IPMI device", "target", target.host, "error", err) return nil, err } return client, nil } prometheus-ipmi-exporter-1.10.0/collector_bmc.go000066400000000000000000000046101475317514000217340ustar00rootroot00000000000000// 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/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", "bmc_url"}, 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 { logger.Error("Failed to collect BMC data", "target", targetName(target.host), "error", err) return 0, err } manufacturerID, err := freeipmi.GetBMCInfoManufacturerID(result) if err != nil { logger.Error("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. logger.Debug("Failed to parse bmc-info data", "target", targetName(target.host), "error", err) systemFirmwareVersion = "N/A" } bmcURL, err := freeipmi.GetBMCInfoBmcURL(result) if err != nil { // This one is not always available. logger.Debug("Failed to parse bmc-info data", "target", targetName(target.host), "error", err) bmcURL = "N/A" } ch <- prometheus.MustNewConstMetric( bmcInfoDesc, prometheus.GaugeValue, 1, firmwareRevision, manufacturerID, systemFirmwareVersion, bmcURL, ) return 1, nil } prometheus-ipmi-exporter-1.10.0/collector_bmc_native.go000066400000000000000000000047741475317514000233150ustar00rootroot00000000000000// Copyright 2025 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 ( "context" "strconv" "github.com/bougou/go-ipmi" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) var ( bmcNativeInfoDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc", "info"), "Constant metric with value '1' providing details about the BMC.", []string{"firmware_revision", "manufacturer", "manufacturer_id", "system_firmware_version"}, nil, ) ) type BMCNativeCollector struct{} func (c BMCNativeCollector) Name() CollectorName { // The name is intentionally the same as the non-native collector return BMCCollectorName } func (c BMCNativeCollector) Cmd() string { return "" // native collector => empty command } func (c BMCNativeCollector) Args() []string { return []string{} } func (c BMCNativeCollector) Collect(_ freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { client, err := NewNativeClient(target) if err != nil { return 0, err } res, err := client.GetDeviceID(context.TODO()) if err != nil { return 0, err } // The API looks slightly awkward here, but doing this instead of calling // client.GetSystemInfo() greatly reduces the number of required round-trips. systemInfo := ipmi.SystemInfoParams{ SystemFirmwareVersions: make([]*ipmi.SystemInfoParam_SystemFirmwareVersion, 0), } err = client.GetSystemInfoParamsFor(context.TODO(), &systemInfo) // This one is not always available systemFirmwareVersion := "N/A" if err != nil { logger.Debug("Failed to get system firmware version", "target", targetName(target.host), "error", err) } else { systemFirmwareVersion = systemInfo.ToSystemInfo().SystemFirmwareVersion } ch <- prometheus.MustNewConstMetric( bmcNativeInfoDesc, prometheus.GaugeValue, 1, res.FirmwareVersionStr(), ipmi.OEM(res.ManufacturerID).String(), strconv.FormatUint(uint64(res.ManufacturerID), 10), systemFirmwareVersion, ) return 1, nil } prometheus-ipmi-exporter-1.10.0/collector_bmc_watchdog.go000066400000000000000000000145641475317514000236250ustar00rootroot00000000000000// 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/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 { logger.Error("Failed to collect BMC watchdog timer", "target", targetName(target.host), "error", err) return 0, err } currentTimerUse, err := freeipmi.GetBMCWatchdogTimerUse(result) if err != nil { logger.Error("Failed to collect BMC watchdog timer use", "target", targetName(target.host), "error", err) return 0, err } loggingState, err := freeipmi.GetBMCWatchdogLoggingState(result) if err != nil { logger.Error("Failed to collect BMC watchdog logging", "target", targetName(target.host), "error", err) return 0, err } currentTimeoutAction, err := freeipmi.GetBMCWatchdogTimeoutAction(result) if err != nil { logger.Error("Failed to collect BMC watchdog timeout action", "target", targetName(target.host), "error", err) return 0, err } currentPretimeoutInterrupt, err := freeipmi.GetBMCWatchdogPretimeoutInterrupt(result) if err != nil { logger.Error("Failed to collect BMC watchdog pretimeout interrupt", "target", targetName(target.host), "error", err) return 0, err } pretimeoutInterval, err := freeipmi.GetBMCWatchdogPretimeoutInterval(result) if err != nil { logger.Error("Failed to collect BMC watchdog pretimeout interval", "target", targetName(target.host), "error", err) return 0, err } initialCountdown, err := freeipmi.GetBMCWatchdogInitialCountdown(result) if err != nil { logger.Error("Failed to collect BMC watchdog initial countdown", "target", targetName(target.host), "error", err) return 0, err } currentCountdown, err := freeipmi.GetBMCWatchdogCurrentCountdown(result) if err != nil { logger.Error("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.10.0/collector_bmc_watchdog_native.go000066400000000000000000000122171475317514000251640ustar00rootroot00000000000000// Copyright 2025 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 ( "context" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) var ( bmcWatchdogNativeTimerDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "timer_state"), "Watchdog timer running (1: running, 0: stopped)", []string{}, nil, ) // TODO add "Reserved" (0x0)? also needed in lib watchdogNativeTimerUses = []string{"BIOS FRB2", "BIOS/POST", "OS Load", "SMS/OS", "OEM"} bmcWatchdogNativeTimerUseDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "timer_use_state"), "Watchdog timer use (1: active, 0: inactive)", []string{"name"}, nil, ) bmcWatchdogNativeLoggingDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "logging_state"), "Watchdog log flag (1: Enabled, 0: Disabled / note: reverse of freeipmi)", []string{}, nil, ) watchdogNativeTimeoutActions = []string{"No action", "Hard Reset", "Power Down", "Power Cycle"} bmcWatchdogNativeTimeoutActionDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "timeout_action_state"), "Watchdog timeout action (1: active, 0: inactive)", []string{"action"}, nil, ) watchdogNativePretimeoutInterrupts = []string{"None", "SMI", "NMI / Diagnostic Interrupt", "Messaging Interrupt"} bmcWatchdogNativePretimeoutInterruptDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "pretimeout_interrupt_state"), "Watchdog pre-timeout interrupt (1: active, 0: inactive)", []string{"interrupt"}, nil, ) bmcWatchdogNativePretimeoutIntervalDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "pretimeout_interval_seconds"), "Watchdog pre-timeout interval in seconds", []string{}, nil, ) bmcWatchdogNativeInitialCountdownDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "initial_countdown_seconds"), "Watchdog initial countdown in seconds", []string{}, nil, ) bmcWatchdogNativeCurrentCountdownDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc_watchdog", "current_countdown_seconds"), "Watchdog initial countdown in seconds", []string{}, nil, ) ) type BMCWatchdogNativeCollector struct{} func (c BMCWatchdogNativeCollector) Name() CollectorName { // The name is intentionally the same as the non-native collector return BMCWatchdogCollectorName } func (c BMCWatchdogNativeCollector) Cmd() string { return "" } func (c BMCWatchdogNativeCollector) Args() []string { return []string{} } func (c BMCWatchdogNativeCollector) Collect(_ freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { // TODO this now works remotely client, err := NewNativeClient(target) if err != nil { return 0, err } res, err := client.GetWatchdogTimer(context.TODO()) if err != nil { return 0, err } ch <- prometheus.MustNewConstMetric(bmcWatchdogNativeTimerDesc, prometheus.GaugeValue, boolToFloat(res.TimerIsStarted)) for _, timerUse := range watchdogNativeTimerUses { if res.TimerUse.String() == timerUse { ch <- prometheus.MustNewConstMetric(bmcWatchdogNativeTimerUseDesc, prometheus.GaugeValue, 1, timerUse) } else { ch <- prometheus.MustNewConstMetric(bmcWatchdogNativeTimerUseDesc, prometheus.GaugeValue, 0, timerUse) } } ch <- prometheus.MustNewConstMetric(bmcWatchdogNativeLoggingDesc, prometheus.GaugeValue, boolToFloat(!res.DontLog)) for _, timeoutAction := range watchdogNativeTimeoutActions { if res.TimeoutAction.String() == timeoutAction { ch <- prometheus.MustNewConstMetric(bmcWatchdogNativeTimeoutActionDesc, prometheus.GaugeValue, 1, timeoutAction) } else { ch <- prometheus.MustNewConstMetric(bmcWatchdogNativeTimeoutActionDesc, prometheus.GaugeValue, 0, timeoutAction) } } for _, pretimeoutInterrupt := range watchdogNativePretimeoutInterrupts { if res.PreTimeoutInterrupt.String() == pretimeoutInterrupt { ch <- prometheus.MustNewConstMetric(bmcWatchdogNativePretimeoutInterruptDesc, prometheus.GaugeValue, 1, pretimeoutInterrupt) } else { ch <- prometheus.MustNewConstMetric(bmcWatchdogNativePretimeoutInterruptDesc, prometheus.GaugeValue, 0, pretimeoutInterrupt) } } ch <- prometheus.MustNewConstMetric(bmcWatchdogNativePretimeoutIntervalDesc, prometheus.GaugeValue, float64(res.PreTimeoutIntervalSec)) ch <- prometheus.MustNewConstMetric(bmcWatchdogNativeInitialCountdownDesc, prometheus.GaugeValue, float64(res.InitialCountdown)) ch <- prometheus.MustNewConstMetric(bmcWatchdogNativeCurrentCountdownDesc, prometheus.GaugeValue, float64(res.PresentCountdown)) return 1, nil } prometheus-ipmi-exporter-1.10.0/collector_chassis.go000066400000000000000000000052671475317514000226410ustar00rootroot00000000000000// 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/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 { logger.Error("Failed to collect chassis data", "target", targetName(target.host), "error", err) return 0, err } currentChassisDriveFault, err := freeipmi.GetChassisDriveFault(result) if err != nil { logger.Error("Failed to collect chassis data", "target", targetName(target.host), "error", err) return 0, err } currentChassisCoolingFault, err := freeipmi.GetChassisCoolingFault(result) if err != nil { logger.Error("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.10.0/collector_chassis_native.go000066400000000000000000000046571475317514000242110ustar00rootroot00000000000000// Copyright 2025 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 ( "context" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) var ( chassisNativePowerStateDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "chassis", "power_state"), "Current power state (1=on, 0=off).", []string{}, nil, ) chassisNativeDriveFaultDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "chassis", "drive_fault_state"), "Current drive fault state (1=true, 0=false).", // TODO value mapping changed []string{}, nil, ) chassisNativeCoolingFaultDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "chassis", "cooling_fault_state"), "Current Cooling/fan fault state (1=true, 0=false).", // TODO value mapping changed []string{}, nil, ) ) type ChassisNativeCollector struct{} func (c ChassisNativeCollector) Name() CollectorName { // The name is intentionally the same as the non-native collector return ChassisCollectorName } func (c ChassisNativeCollector) Cmd() string { return "" } func (c ChassisNativeCollector) Args() []string { return []string{} } func (c ChassisNativeCollector) Collect(_ freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { client, err := NewNativeClient(target) if err != nil { return 0, err } res, err := client.GetChassisStatus(context.TODO()) if err != nil { return 0, err } ch <- prometheus.MustNewConstMetric( chassisNativePowerStateDesc, prometheus.GaugeValue, boolToFloat(res.PowerIsOn), ) ch <- prometheus.MustNewConstMetric( chassisNativeDriveFaultDesc, prometheus.GaugeValue, boolToFloat(res.DriveFault), ) ch <- prometheus.MustNewConstMetric( chassisNativeCoolingFaultDesc, prometheus.GaugeValue, boolToFloat(res.CollingFanFault), ) return 1, nil } func boolToFloat(b bool) float64 { if b { return 1.0 } return 0.0 } prometheus-ipmi-exporter-1.10.0/collector_dcmi.go000066400000000000000000000034031475317514000221060ustar00rootroot00000000000000// 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/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 { logger.Error("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.10.0/collector_dcmi_native.go000066400000000000000000000034241475317514000234570ustar00rootroot00000000000000// Copyright 2025 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 ( "context" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) var ( powerConsumptionNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "dcmi", "power_consumption_watts"), "Current power consumption in Watts.", []string{}, nil, ) ) type DCMINativeCollector struct{} func (c DCMINativeCollector) Name() CollectorName { // The name is intentionally the same as the non-native collector return DCMICollectorName } func (c DCMINativeCollector) Cmd() string { return "" } func (c DCMINativeCollector) Args() []string { return []string{} } func (c DCMINativeCollector) Collect(_ freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { client, err := NewNativeClient(target) if err != nil { return 0, err } res, err := client.GetDCMIPowerReading(context.TODO()) if err != nil { logger.Error("Failed to collect DCMI data", "target", targetName(target.host), "error", err) return 0, err } if res.PowerMeasurementActive { ch <- prometheus.MustNewConstMetric( powerConsumptionNativeDesc, prometheus.GaugeValue, float64(res.CurrentPower), ) } return 1, nil } prometheus-ipmi-exporter-1.10.0/collector_ipmi.go000066400000000000000000000145771475317514000221460ustar00rootroot00000000000000// 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/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{ "--quiet-cache", "--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 { logger.Error("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: logger.Error("Unknown sensor state", "target", targetHost, "state", data.State) state = math.NaN() } logger.Debug("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.10.0/collector_ipmi_native.go000066400000000000000000000164101475317514000235000ustar00rootroot00000000000000// Copyright 2025 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 ( "context" "fmt" "math" "strconv" "github.com/bougou/go-ipmi" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) var ( sensorStateNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sensor", "state"), "Indicates the severity of the state reported by an IPMI sensor (0=nominal, 1=warning, 2=critical, 3=non-recoverable).", []string{"id", "name", "type"}, nil, ) sensorValueNativeDesc = 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, ) fanSpeedRPMNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "fan_speed", "rpm"), "Fan speed in rotations per minute.", []string{"id", "name"}, nil, ) fanSpeedRatioNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "fan_speed", "ratio"), "Fan speed as a proportion of the maximum speed.", []string{"id", "name"}, nil, ) fanSpeedStateNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "fan_speed", "state"), "Reported state of a fan speed sensor (0=nominal, 1=warning, 2=critical, 3=non-recoverable).", []string{"id", "name"}, nil, ) temperatureNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "temperature", "celsius"), "Temperature reading in degree Celsius.", []string{"id", "name"}, nil, ) temperatureStateNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "temperature", "state"), "Reported state of a temperature sensor (0=nominal, 1=warning, 2=critical, 3=non-recoverable).", []string{"id", "name"}, nil, ) voltageNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "voltage", "volts"), "Voltage reading in Volts.", []string{"id", "name"}, nil, ) voltageStateNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "voltage", "state"), "Reported state of a voltage sensor (0=nominal, 1=warning, 2=critical, 3=non-recoverable).", []string{"id", "name"}, nil, ) currentNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "current", "amperes"), "Current reading in Amperes.", []string{"id", "name"}, nil, ) currentStateNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "current", "state"), "Reported state of a current sensor (0=nominal, 1=warning, 2=critical, 3=non-recoverable).", []string{"id", "name"}, nil, ) powerNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "power", "watts"), "Power reading in Watts.", []string{"id", "name"}, nil, ) powerStateNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "power", "state"), "Reported state of a power sensor (0=nominal, 1=warning, 2=critical, 3=non-recoverable).", []string{"id", "name"}, nil, ) ) type IPMINativeCollector struct{} func (c IPMINativeCollector) Name() CollectorName { // The name is intentionally the same as the non-native collector return IPMICollectorName } func (c IPMINativeCollector) Cmd() string { return "" } func (c IPMINativeCollector) Args() []string { return []string{} } func (c IPMINativeCollector) Collect(_ freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { excludeIDs := target.config.ExcludeSensorIDs targetHost := targetName(target.host) filter := func(sensor *ipmi.Sensor) bool { for _, id := range excludeIDs { if id == int64(sensor.Number) { return false } } return true } client, err := NewNativeClient(target) if err != nil { return 0, err } res, err := client.GetSensors(context.TODO(), filter) if err != nil { return 0, err } // results, err := freeipmi.GetSensorData(result, excludeIds) // if err != nil { // logger.Error("Failed to collect sensor data", "target", targetHost, "error", err) // return 0, err // } for _, data := range res { var state float64 switch data.Status() { case "ok": state = 0 case "lnc", "unc": // lower/upper non-critical state = 1 case "lcr", "ucr": // lower/upper critical state = 2 case "lnr", "unr": // lower/upper non-recoverable state = 3 // TODO this is new case "N/A": state = math.NaN() default: logger.Error("Unknown sensor state", "target", targetHost, "state", data.Status()) state = math.NaN() } logger.Debug("Got values", "target", targetHost, "data", fmt.Sprintf("%+v", data)) // TODO this could be greatly improved, now that we have structured data available switch data.SensorUnit.BaseUnit { case ipmi.SensorUnitType_RPM: if data.SensorUnit.Percentage { collectTypedSensorNative(ch, fanSpeedRatioNativeDesc, fanSpeedStateNativeDesc, state, data, 0.01) } else { collectTypedSensorNative(ch, fanSpeedRPMNativeDesc, fanSpeedStateNativeDesc, state, data, 1.0) } case ipmi.SensorUnitType_DegreesC: collectTypedSensorNative(ch, temperatureNativeDesc, temperatureStateNativeDesc, state, data, 1.0) case ipmi.SensorUnitType_Amps: collectTypedSensorNative(ch, currentNativeDesc, currentStateNativeDesc, state, data, 1.0) case ipmi.SensorUnitType_Volts: collectTypedSensorNative(ch, voltageNativeDesc, voltageStateNativeDesc, state, data, 1.0) case ipmi.SensorUnitType_Watts: collectTypedSensorNative(ch, powerNativeDesc, powerStateNativeDesc, state, data, 1.0) default: collectGenericSensorNative(ch, state, data) } } return 1, nil } func (c IPMINativeCollector) 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 collectTypedSensorNative(ch chan<- prometheus.Metric, desc, stateDesc *prometheus.Desc, state float64, data *ipmi.Sensor, scale float64) { ch <- prometheus.MustNewConstMetric( desc, prometheus.GaugeValue, data.Value*scale, strconv.FormatInt(int64(data.Number), 10), data.Name, ) ch <- prometheus.MustNewConstMetric( stateDesc, prometheus.GaugeValue, state, strconv.FormatInt(int64(data.Number), 10), data.Name, ) } func collectGenericSensorNative(ch chan<- prometheus.Metric, state float64, data *ipmi.Sensor) { ch <- prometheus.MustNewConstMetric( sensorValueNativeDesc, prometheus.GaugeValue, data.Value, strconv.FormatInt(int64(data.Number), 10), data.Name, data.SensorType.String(), ) ch <- prometheus.MustNewConstMetric( sensorStateNativeDesc, prometheus.GaugeValue, state, strconv.FormatInt(int64(data.Number), 10), data.Name, data.SensorType.String(), ) } prometheus-ipmi-exporter-1.10.0/collector_sel.go000066400000000000000000000040521475317514000217560ustar00rootroot00000000000000// 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/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 { logger.Error("Failed to collect SEL data", "target", targetName(target.host), "error", err) return 0, err } freeSpace, err := freeipmi.GetSELInfoFreeSpace(result) if err != nil { logger.Error("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.10.0/collector_sel_events.go000066400000000000000000000105171475317514000233450ustar00rootroot00000000000000// 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/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) const ( SELEventsCollectorName CollectorName = "sel-events" SELDateTimeFormat string = "Jan-02-2006 15:04:05" ) 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{ "--quiet-cache", "--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 { logger.Error("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 } var newTimestamp float64 for _, data := range events { for _, metricConfig := range selEventConfigs { match := metricConfig.Regex.FindStringSubmatch(data.Event) if match != nil { newTimestamp = 0.0 datetime := data.Date + " " + data.Time t, err := time.Parse(SELDateTimeFormat, datetime) // ignore errors with invalid date or time // NOTE: in some cases ipmi-sel can return "PostInit" in Date and Time fields // Example: // $ ipmi-sel --comma-separated-output --output-event-state --interpret-oem-data --output-oem-event-strings // ID,Date,Time,Name,Type,State,Event // 3,PostInit,PostInit,Sensor #211,Memory,Warning,Correctable memory error ; Event Data3 = 34h if err != nil { logger.Debug("Failed to parse time", "target", targetName(target.host), "error", err) } else { 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.10.0/collector_sel_events_native.go000066400000000000000000000105201475317514000247050ustar00rootroot00000000000000// Copyright 2025 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 ( "context" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) var ( selEventsCountByStateNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel_events", "count_by_state"), "Current number of log entries in the SEL by state.", []string{"state"}, nil, ) selEventsCountByNameNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel_events", "count_by_name"), "Current number of custom log entries in the SEL by name.", []string{"name"}, nil, ) selEventsLatestTimestampNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel_events", "latest_timestamp"), "Latest timestamp of custom log entries in the SEL by name.", []string{"name"}, nil, ) ) type SELEventsNativeCollector struct{} func (c SELEventsNativeCollector) Name() CollectorName { // The name is intentionally the same as the non-native collector return SELEventsCollectorName } func (c SELEventsNativeCollector) Cmd() string { return "" } func (c SELEventsNativeCollector) Args() []string { return []string{} } func (c SELEventsNativeCollector) Collect(_ freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { selEventConfigs := target.config.SELEvents client, err := NewNativeClient(target) if err != nil { return 0, err } res, err := client.GetSELEntries(context.TODO(), 0) if err != nil { 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 res { for _, metricConfig := range selEventConfigs { match := metricConfig.Regex.FindStringSubmatch(data.Standard.EventString()) logger.Debug("event regex", "regex", metricConfig.RegexRaw, "input", data.Standard.EventString(), "match", match) if match != nil { var newTimestamp = float64(data.Standard.Timestamp.Unix()) // datetime := data.Date + " " + data.Time // t, err := time.Parse(SELDateTimeFormat, datetime) // ignore errors with invalid date or time // NOTE: in some cases ipmi-sel can return "PostInit" in Date and Time fields // Example: // $ ipmi-sel --comma-separated-output --output-event-state --interpret-oem-data --output-oem-event-strings // ID,Date,Time,Name,Type,State,Event // 3,PostInit,PostInit,Sensor #211,Memory,Warning,Correctable memory error ; Event Data3 = 34h // if err != nil { // logger.Debug("Failed to parse time", "target", targetName(target.host), "error", err) // } else { // 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 state := string(data.Standard.EventSeverity()) _, ok := selEventByStateCount[state] if !ok { selEventByStateCount[state] = 0 } selEventByStateCount[state]++ } for state, value := range selEventByStateCount { ch <- prometheus.MustNewConstMetric( selEventsCountByStateNativeDesc, prometheus.GaugeValue, value, state, ) } for name, value := range selEventByNameCount { ch <- prometheus.MustNewConstMetric( selEventsCountByNameNativeDesc, prometheus.GaugeValue, value, name, ) ch <- prometheus.MustNewConstMetric( selEventsLatestTimestampNativeDesc, prometheus.GaugeValue, selEventByNameTimestamp[name], name, ) } return 1, nil } prometheus-ipmi-exporter-1.10.0/collector_sel_native.go000066400000000000000000000036441475317514000233320ustar00rootroot00000000000000// Copyright 2025 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 ( "context" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) var ( selEntriesCountNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel", "logs_count"), "Current number of log entries in the SEL.", []string{}, nil, ) selFreeSpaceNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "sel", "free_space_bytes"), "Current free space remaining for new SEL entries.", []string{}, nil, ) ) type SELNativeCollector struct{} func (c SELNativeCollector) Name() CollectorName { // The name is intentionally the same as the non-native collector return SELCollectorName } func (c SELNativeCollector) Cmd() string { return "" } func (c SELNativeCollector) Args() []string { return []string{""} } func (c SELNativeCollector) Collect(_ freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { client, err := NewNativeClient(target) if err != nil { return 0, err } res, err := client.GetSELInfo(context.TODO()) if err != nil { return 0, err } ch <- prometheus.MustNewConstMetric( selEntriesCountNativeDesc, prometheus.GaugeValue, float64(res.Entries), ) ch <- prometheus.MustNewConstMetric( selFreeSpaceNativeDesc, prometheus.GaugeValue, float64(res.FreeBytes), ) return 1, nil } prometheus-ipmi-exporter-1.10.0/collector_sm_lan_mode.go000066400000000000000000000042141475317514000234500ustar00rootroot00000000000000// 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/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 { logger.Error("Failed to collect LAN mode data", "target", targetName(target.host), "error", err) return 0, err } if len(octets) != 3 { logger.Error("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: logger.Error("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.10.0/collector_sm_lan_mode_native.go000066400000000000000000000050321475317514000250150ustar00rootroot00000000000000// Copyright 2025 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 ( "context" "fmt" "github.com/bougou/go-ipmi" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" ) var ( lanModeNativeDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "config", "lan_mode"), "Returns configured LAN mode (0=dedicated, 1=shared, 2=failover).", nil, nil, ) ) type SMLANModeNativeCollector struct{} func (c SMLANModeNativeCollector) Name() CollectorName { // The name is intentionally the same as the non-native collector return SMLANModeCollectorName } func (c SMLANModeNativeCollector) Cmd() string { return "" } func (c SMLANModeNativeCollector) Args() []string { return []string{} } func (c SMLANModeNativeCollector) Collect(_ freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { client, err := NewNativeClient(target) if err != nil { return 0, err } if _, err := client.SetSessionPrivilegeLevel(context.TODO(), ipmi.PrivilegeLevelAdministrator); err != nil { logger.Error("Failed to set privilege level to admin", "target", targetName(target.host)) return 0, fmt.Errorf("failed to set privilege level to admin") } res, err := client.RawCommand(context.TODO(), ipmi.NetFnOEMSupermicroRequest, 0x70, []byte{0x0C, 0x00}, "GetSupermicroLanMode") if err != nil { logger.Error("raw command failed", "error", err) return 0, err } if len(res.Response) != 1 { logger.Error("Unexpected number of octets", "target", targetName(target.host), "octets", len(res.Response)) return 0, fmt.Errorf("unexpected number of octets in raw response: %d", len(res.Response)) } value := res.Response[0] switch value { case 0, 1, 2: ch <- prometheus.MustNewConstMetric(lanModeNativeDesc, prometheus.GaugeValue, float64(value)) default: logger.Error("Unexpected lan mode status (ipmi-raw)", "target", targetName(target.host), "status", value) return 0, fmt.Errorf("unexpected lan mode status: %d", value) } return 1, nil } prometheus-ipmi-exporter-1.10.0/config.go000066400000000000000000000207431475317514000203770ustar00rootroot00000000000000// 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/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 defaultArgs []string customArgs []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.customArgs != nil { // custom args come first, this way it is quite easy to // override a collector to use e.g. sudo args = append(args, c.customArgs...) } if c.defaultArgs != nil { args = append(args, c.defaultArgs...) } 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: if *nativeIPMI { return IPMINativeCollector{}, nil } return IPMICollector{}, nil case BMCCollectorName: if *nativeIPMI { return BMCNativeCollector{}, nil } return BMCCollector{}, nil case BMCWatchdogCollectorName: if *nativeIPMI { return BMCWatchdogNativeCollector{}, nil } return BMCWatchdogCollector{}, nil case SELCollectorName: if *nativeIPMI { return SELNativeCollector{}, nil } return SELCollector{}, nil case SELEventsCollectorName: if *nativeIPMI { return SELEventsNativeCollector{}, nil } return SELEventsCollector{}, nil case DCMICollectorName: if *nativeIPMI { return DCMINativeCollector{}, nil } return DCMICollector{}, nil case ChassisCollectorName: if *nativeIPMI { return ChassisNativeCollector{}, nil } return ChassisCollector{}, nil case SMLANModeCollectorName: if *nativeIPMI { return SMLANModeNativeCollector{}, nil } 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 (s *IPMIConfig) GetCollectors() []collector { result := []collector{} for _, co := range s.Collectors { // At this point validity has already been checked i, _ := co.GetInstance() cc := ConfiguredCollector{ collector: i, command: s.CollectorCmd[i.Name()], defaultArgs: s.CollectorArgs[i.Name()], customArgs: s.CustomArgs[i.Name()], } result = append(result, cc) } return result } func (s *IPMIConfig) GetFreeipmiConfig() string { var b strings.Builder if s.Driver != "" { fmt.Fprintf(&b, "driver-type %s\n", s.Driver) } if s.Privilege != "" { fmt.Fprintf(&b, "privilege-level %s\n", s.Privilege) } if s.User != "" { fmt.Fprintf(&b, "username %s\n", s.User) } if s.Password != "" { fmt.Fprintf(&b, "password %s\n", freeipmi.EscapePassword(s.Password)) } if s.Timeout != 0 { fmt.Fprintf(&b, "session-timeout %d\n", s.Timeout) } if len(s.WorkaroundFlags) > 0 { fmt.Fprintf(&b, "workaround-flags") for _, flag := range s.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 { logger.Error("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 != "" { logger.Info("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 { logger.Error("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 logger.Debug("Needed default config for, but none configured, using FreeIPMI defaults", "target", targetName(target)) config = defaultConfig } } return config } prometheus-ipmi-exporter-1.10.0/contrib/000077500000000000000000000000001475317514000202355ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/contrib/rpm/000077500000000000000000000000001475317514000210335ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/contrib/rpm/README.md000066400000000000000000000016001475317514000223070ustar00rootroot00000000000000# 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.10.0/contrib/rpm/build.sh000077500000000000000000000030111475317514000224640ustar00rootroot00000000000000#!/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.10.0/contrib/rpm/config/000077500000000000000000000000001475317514000223005ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/contrib/rpm/config/prometheus-ipmi-exporter.yml000066400000000000000000000010411475317514000300140ustar00rootroot00000000000000# 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.10.0/contrib/rpm/docker/000077500000000000000000000000001475317514000223025ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/contrib/rpm/docker/Dockerfile-centos7000066400000000000000000000007511475317514000256570ustar00rootroot00000000000000FROM 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.10.0/contrib/rpm/prometheus-ipmi-exporter.spec000066400000000000000000000030561475317514000267100ustar00rootroot00000000000000%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.10.0/contrib/rpm/sudoers/000077500000000000000000000000001475317514000225175ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/contrib/rpm/sudoers/prometheus-ipmi-exporter000066400000000000000000000007341475317514000274430ustar00rootroot00000000000000# !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.10.0/contrib/rpm/systemd/000077500000000000000000000000001475317514000225235ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/contrib/rpm/systemd/prometheus-ipmi-exporter.service000066400000000000000000000004341475317514000311030ustar00rootroot00000000000000[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.10.0/docker-compose.yml000066400000000000000000000005751475317514000222410ustar00rootroot00000000000000version: '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.10.0/docs/000077500000000000000000000000001475317514000175255ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/docs/configuration.md000066400000000000000000000114471475317514000227250ustar00rootroot00000000000000# Configuration Simply scraping the standard `/metrics` endpoint will make the exporter emit local IPMI metrics. If the exporter is running with sufficient privileges, no special configuration is required. See the [privileges document](privileges.md) for more details. For remote metrics, the general configuration pattern is that of a [multi-target exporter][multi-target]. Please read that guide to get the general idea about this approach. [multi-target]: https://prometheus.io/docs/guides/multi-target-exporter/ "Understanding and using the multi-target exporter pattern - Prometheus docs" We offer this approach as IPMI devices often provide useful information even while the supervised host is turned off. Also, you can have a single exporter instance 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.10.0/docs/metrics.md000066400000000000000000000240351475317514000215210ustar00rootroot00000000000000# Exported metrics ## Exporter meta data - `ipmi_exporter_build_info`: standard build meta data provided by the Prometheus `client_golang` package A metric with a constant '1' value labeled by version, revision, branch, goversion from which the exporter was built, and the goos and goarch for the build. ## 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.10.0/docs/native.md000066400000000000000000000060251475317514000213400ustar00rootroot00000000000000# Go-native IPMI implementation The IPMI exporter now supports using a Go-native IPMI implementation, powered by [go-ipmi](https://github.com/bougou/go-ipmi). In doing so, the exporter can run *without* the FreeIPMI suite of tools. This mode of operation is currently considered experimental. But it is being actively maintained/developed, and is likely the future of this exporter. ## Should I use it? In general, if you have the time to spare, it would be great if you could give this a spin and provide [feedback](https://github.com/prometheus-community/ipmi_exporter/issues) if you can spot anything is _not_ working in native-IPMI mode that _is_ working via FreeIPMI (the default mode). Besides that, the native implementation also offers some real benefits: * No more execution of external commands * If you are affected by #227 - this cannot happen with native IPMI * Some collectors may require less round-trips, as the exporter has more control over the IPMI calls being made * The BMC watchdog collector now works remotely * In the future, as the native implementation matures, it might provide better data in certain situations ## How do I use it? Simply run the exporter with `--native-ipmi`. But please make sure to read the rest of this document. ## What to watch out for? There are some subtle differences to be aware of, compared to the FreeIPMI-based collectors: * **All collectors:** * The following config items no longer have any effect: * `driver` (only local and `LAN_2_0` are supported, please open an issue if you rely on another driver) * `workaround_flags` (not supported by go-ipmi, please open an issue if you rely on this) * `collector_cmd`, `collector_args`, `custom_cmd` - no longer applicable, please see also privileges section below * The `privilege` config item should no longer be needed (FreeIPMI restricts this to "OPERATOR" by default, but go-ipmi does not) * **ipmi collector:** sensors can now have a `state` value of `3` ("non-recoverable") - a value that FreeIPMI does not provide * **chassis collector:** in the native collector, the representation changed from `"Current drive fault state (1=false, 0=true)."` to `"Current drive fault state (1=true, 0=false)."`, simply because the current representation is weird and will likely also be changed in a future major release; same thing for the fan fault state * **bmc collector:** this needs some testing, specifically the `system_firmware_revision`, as not all hardware supports this ## Privileges Since no external commands are executed in native IPMI mode, none of the `sudo` trickery described in [privileges](https://github.com/prometheus-community/ipmi_exporter/blob/master/docs/privileges.md) will work anymore. Make sure the exporters runs as a user that has access to the local IPMI device (for local scraping, for remote scraping no special privileges should be required) ## Feedback Please [open an issue](https://github.com/prometheus-community/ipmi_exporter/issues) if you run into problems using the native IPMI mode. prometheus-ipmi-exporter-1.10.0/docs/privileges.md000066400000000000000000000023121475317514000222160ustar00rootroot00000000000000# Privileges If you are running the exporter as unprivileged user, but need to execute the FreeIPMI tools as root (as is likely necessary to access the local IPMI interface), you can do the following: **NOTE:** Make sure to adapt all absolute paths to match your distro! 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: /usr/bin/sudo custom_args: ipmi: - "/usr/sbin/ipmimonitoring" ``` See also the [sudo example config](../ipmi_local_sudo.yml). Note that no elevated privileges are necessary for getting remote metrics. prometheus-ipmi-exporter-1.10.0/freeipmi/000077500000000000000000000000001475317514000203755ustar00rootroot00000000000000prometheus-ipmi-exporter-1.10.0/freeipmi/freeipmi.go000066400000000000000000000340301475317514000225240ustar00rootroot00000000000000// 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" "log/slog" "math" "os" "os/exec" "path/filepath" "regexp" "strconv" "strings" "syscall" ) 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