pax_global_header00006660000000000000000000000064136237647700014531gustar00rootroot0000000000000052 comment=dd29e29c54c9d5d8ec86d72c2256f5ce743d62d5 prometheus-ipmi-exporter-1.1.0+ds/000077500000000000000000000000001362376477000171275ustar00rootroot00000000000000prometheus-ipmi-exporter-1.1.0+ds/.cirrus.yml000066400000000000000000000007741362376477000212470ustar00rootroot00000000000000container: image: quay.io/prometheus/golang-builder:1.13-base build_task: build_script: make release_task: only_if: $CIRRUS_TAG =~ 'v.*' depends_on: build build_script: make build tarball tarball_artifacts: path: "ipmi_exporter-*.tar.gz" type: application/gzip matrix: - env: GOOS: linux GOARCH: amd64 - env: GOOS: darwin GOARCH: amd64 - env: GOOS: freebsd GOARCH: amd64 - env: GOOS: linux GOARCH: arm64 prometheus-ipmi-exporter-1.1.0+ds/.gitignore000066400000000000000000000000271362376477000211160ustar00rootroot00000000000000ipmi_exporter *.tar.gz prometheus-ipmi-exporter-1.1.0+ds/.promu.yml000066400000000000000000000012151362376477000210710ustar00rootroot00000000000000go: # Whenever the Go version is updated here, # .cirrus.yml should also be updated. version: 1.13 repository: path: github.com/soundcloud/ipmi_exporter build: flags: -mod=vendor -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 prometheus-ipmi-exporter-1.1.0+ds/CONTRIBUTING.md000066400000000000000000000034321362376477000213620ustar00rootroot00000000000000## 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/soundcloud/ipmi_exporter/issues [pr]: http://help.github.com/send-pull-requests prometheus-ipmi-exporter-1.1.0+ds/Dockerfile000066400000000000000000000010501362376477000211150ustar00rootroot00000000000000# Build /go/bin/ipmi_exporter FROM quay.io/prometheus/golang-builder:1.13-base AS builder ADD . /go/src/github.com/soundcloud/ipmi_exporter/ RUN cd /go/src/github.com/soundcloud/ipmi_exporter && make # Container image FROM ubuntu:18.04 WORKDIR / RUN apt-get update \ && apt-get install freeipmi-tools -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /go/src/github.com/soundcloud/ipmi_exporter/ipmi_exporter /bin/ipmi_exporter EXPOSE 9290 ENTRYPOINT ["/bin/ipmi_exporter"] CMD ["--config.file", "/config.yml"] prometheus-ipmi-exporter-1.1.0+ds/LICENSE000066400000000000000000000021251362376477000201340ustar00rootroot00000000000000The MIT License Copyright (c) 2019 SoundCloud Ltd. and the IPMI exporter developers 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.1.0+ds/Makefile000066400000000000000000000003441362376477000205700ustar00rootroot00000000000000# Override the default common all. .PHONY: all all: precheck style unused build test DOCKER_ARCHS ?= amd64 DOCKER_IMAGE_NAME ?= ipmi-exporter DOCKER_REPO ?= soundcloud include Makefile.common docker: common-docker prometheus-ipmi-exporter-1.1.0+ds/Makefile.common000066400000000000000000000217771362376477000220740ustar00rootroot00000000000000# Copyright 2018 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # A common Makefile that includes rules to be reused in different prometheus projects. # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! # Example usage : # Create the main Makefile in the root project directory. # include Makefile.common # customTarget: # @echo ">> Running customTarget" # # Ensure GOBIN is not set during build so that promu is installed to the correct path unexport GOBIN GO ?= go GOFMT ?= $(GO)fmt FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) GOOPTS ?= GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) GO_VERSION ?= $(shell $(GO) version) GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') GOVENDOR := GO111MODULE := ifeq (, $(PRE_GO_111)) ifneq (,$(wildcard go.mod)) # Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI). GO111MODULE := on ifneq (,$(wildcard vendor)) # Always use the local vendor/ directory to satisfy the dependencies. GOOPTS := $(GOOPTS) -mod=vendor endif endif else ifneq (,$(wildcard go.mod)) ifneq (,$(wildcard vendor)) $(warning This repository requires Go >= 1.11 because of Go modules) $(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)') endif else # This repository isn't using Go modules (yet). GOVENDOR := $(FIRST_GOPATH)/bin/govendor endif endif PROMU := $(FIRST_GOPATH)/bin/promu pkgs = ./... ifeq (arm, $(GOHOSTARCH)) GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) else GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) endif PROMU_VERSION ?= 0.5.0 PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_VERSION ?= v1.18.0 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint endif endif PREFIX ?= $(shell pwd) BIN_DIR ?= $(shell pwd) DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) DOCKERFILE_PATH ?= ./Dockerfile DOCKERBUILD_CONTEXT ?= ./ DOCKER_REPO ?= prom DOCKER_ARCHS ?= amd64 BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) ifeq ($(GOHOSTARCH),amd64) ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) # Only supported on amd64 test-flags := -race endif endif # This rule is used to forward a target like "build" to "common-build". This # allows a new "build" target to be defined in a Makefile which includes this # one and override "common-build" without override warnings. %: common-% ; .PHONY: common-all common-all: precheck style check_license lint unused build test .PHONY: common-style common-style: @echo ">> checking code style" @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ if [ -n "$${fmtRes}" ]; then \ echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ echo "Please ensure you are using $$($(GO) version) for formatting code."; \ exit 1; \ fi .PHONY: common-check_license common-check_license: @echo ">> checking license header" @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ done); \ if [ -n "$${licRes}" ]; then \ echo "license header checking failed:"; echo "$${licRes}"; \ exit 1; \ fi .PHONY: common-deps common-deps: @echo ">> getting dependencies" ifdef GO111MODULE GO111MODULE=$(GO111MODULE) $(GO) mod download else $(GO) get $(GOOPTS) -t ./... endif .PHONY: common-test-short common-test-short: @echo ">> running short tests" GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs) .PHONY: common-test common-test: @echo ">> running all tests" GO111MODULE=$(GO111MODULE) $(GO) test $(test-flags) $(GOOPTS) $(pkgs) .PHONY: common-format common-format: @echo ">> formatting code" GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs) .PHONY: common-vet common-vet: @echo ">> vetting code" GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs) .PHONY: common-lint common-lint: $(GOLANGCI_LINT) ifdef GOLANGCI_LINT @echo ">> running golangci-lint" ifdef GO111MODULE # 'go list' needs to be executed before staticcheck to prepopulate the modules cache. # Otherwise staticcheck might fail randomly for some reason not yet explained. GO111MODULE=$(GO111MODULE) $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) else $(GOLANGCI_LINT) run $(pkgs) endif endif # For backward-compatibility. .PHONY: common-staticcheck common-staticcheck: lint .PHONY: common-unused common-unused: $(GOVENDOR) ifdef GOVENDOR @echo ">> running check for unused packages" @$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages' else ifdef GO111MODULE @echo ">> running check for unused/missing packages in go.mod" GO111MODULE=$(GO111MODULE) $(GO) mod tidy ifeq (,$(wildcard vendor)) @git diff --exit-code -- go.sum go.mod else @echo ">> running check for unused packages in vendor/" GO111MODULE=$(GO111MODULE) $(GO) mod vendor @git diff --exit-code -- go.sum go.mod vendor/ endif endif endif .PHONY: common-build common-build: promu @echo ">> building binaries" GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) .PHONY: common-tarball common-tarball: promu @echo ">> building release tarball" $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) .PHONY: common-docker $(BUILD_DOCKER_ARCHS) common-docker: $(BUILD_DOCKER_ARCHS) $(BUILD_DOCKER_ARCHS): common-docker-%: docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \ -f $(DOCKERFILE_PATH) \ --build-arg ARCH="$*" \ --build-arg OS="linux" \ $(DOCKERBUILD_CONTEXT) .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) common-docker-publish: $(PUBLISH_DOCKER_ARCHS) $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) common-docker-tag-latest: $(TAG_DOCKER_ARCHS) $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" .PHONY: common-docker-manifest common-docker-manifest: DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG)) DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" .PHONY: promu promu: $(PROMU) $(PROMU): $(eval PROMU_TMP := $(shell mktemp -d)) curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) mkdir -p $(FIRST_GOPATH)/bin cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu rm -r $(PROMU_TMP) .PHONY: proto proto: @echo ">> generating code from proto files" @./scripts/genproto.sh ifdef GOLANGCI_LINT $(GOLANGCI_LINT): mkdir -p $(FIRST_GOPATH)/bin curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ | sed -e '/install -d/d' \ | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) endif ifdef GOVENDOR .PHONY: $(GOVENDOR) $(GOVENDOR): GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor endif .PHONY: precheck precheck:: define PRECHECK_COMMAND_template = precheck:: $(1)_precheck PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) .PHONY: $(1)_precheck $(1)_precheck: @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ exit 1; \ fi endef prometheus-ipmi-exporter-1.1.0+ds/README.md000066400000000000000000000317301362376477000204120ustar00rootroot00000000000000Prometheus IPMI Exporter ======================== [![Build Status](https://api.cirrus-ci.com/github/soundcloud/ipmi_exporter.svg?branch=master)](https://cirrus-ci.com/github/soundcloud/ipmi_exporter) This is an IPMI exporter for [Prometheus](https://prometheus.io). It supports both the regular `/metrics` endpoint, exposing metrics from the host that the exporter is running on, as well as an `/ipmi` endpoint that supports IPMI over RMCP - one exporter running on one host can be used to monitor a large number of IPMI interfaces by passing the `target` parameter to a scrape. The exporter relies on tools from the [FreeIPMI](https://www.gnu.org/software/freeipmi/) suite for the actual IPMI implementation. ## Installation For most use-cases, simply download the [the latest release](https://github.com/soundcloud/ipmi_exporter/releases). ### Building from source You need a Go development environment. Then, simply run `make` to build the executable: make This uses the common prometheus tooling to build and run some tests. Alternatively, you can use the standard Go tooling, which will install the executable in `$GOPATH/bin`: go get github.com/soundcloud/ipmi_exporter ### Building a Docker container You can build a Docker container with the included `docker` make target: make docker This will not even require Go tooling on the host. See the included [docker compose example](docker-compose.yml) for how to use the resulting container. ## 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](https://www.gnu.org/software/freeipmi/) suite installed: - `ipmimonitoring`/`ipmi-sensors` - `ipmi-dcmi` - `bmc-info` ### Running as unprivileged user If you are running the exporter as unprivileged user, but need to execute the FreeIPMI tools as root, you can do the following: 1. Add sudoers files to permit the following commands ```bash ipmi-exporter ALL = NOPASSWD:/usr/sbin/ipmimonitoring, /usr/sbin/ipmi-sensors, /usr/sbin/ipmi-dcmi, /usr/sbin/bmc-info, /usr/sbin/ipmi-chassis ``` 2. Create the script under user dir with execute permission ```bash #!/bin/sh sudo /usr/sbin/$(basename $0) "$@" ``` 3. Create symlinks under user dir ```bash ln -s /home/ipmi-exporter/[script name] /home/ipmi-exporter/ipmimonitoring ln -s /home/ipmi-exporter/[script name] /home/ipmi-exporter/ipmi-sensors ln -s /home/ipmi-exporter/[script name] /home/ipmi-exporter/ipmi-dcmi ln -s /home/ipmi-exporter/[script name] /home/ipmi-exporter/bmc-info ln -s /home/ipmi-exporter/[script name] /home/ipmi-exporter/ipmi-chassis ```` 4. Execute ipmi-exporter with the option `--freeipmi.path=/home/ipmi-exporter` ### Running in Docker **NOTE:** you should only use Docker for remote metrics. See [Building a Docker container](#building-a-docker-container) and the example `docker-compose.yml`. Edit the `ipmi_remote.yml` file to configure IPMI credentials, then run with: sudo docker-compose up -d By default, the server will bind on `0.0.0.0:9290`. ## Configuration Simply scraping the standard `/metrics` endpoint will make the exporter emit local IPMI metrics. No special configuration is required. For remote metrics, the general configuration pattern is similar to that of the [blackbox exporter](https://github.com/prometheus/blackbox_exporter), i.e. Prometheus scrapes a small number (possibly one) of IPMI exporters with a `target` and `module` URL parameter to tell the exporter which IPMI device it should use to retrieve the IPMI metrics. We offer this approach as IPMI devices often provide useful information even while the supervised host is turned off. If you are running the exporter on a separate host anyway, it makes more sense to have only a few of them, each probing many (possibly thousands of) IPMI devices, rather than one exporter per IPMI device. **NOTE:** If you are using remote metrics, but still want to get the local process metrics from the instance, you must use a `default` module with an empty collectors list and use other modules for the remote hosts. ### IPMI exporter The exporter can read a configuration file by setting `config.file` (see above). To collect local metrics, you might not even need one. For remote metrics, it must contain at least user names and passwords for IPMI access to all targets to be scraped. You can additionally specify the IPMI driver type and privilege level to use (see `man 5 freeipmi.conf` for more details and possible values). The config file supports the notion of "modules", so that different configurations can be re-used for groups of targets. See the section below on how to set the module parameter in Prometheus. The special module "default" is used in case the scrape does not request a specific module. The configuration file also supports a blacklist of sensors, useful in case of OEM-specific sensors that FreeIPMI cannot deal with properly or otherwise misbehaving sensors. This applies to both local and remote metrics. There are two commented example configuration files, see `ipmi_local.yml` for scraping local host metrics and `ipmi_remote.yml` for scraping remote IPMI interfaces. ### Prometheus #### Local metrics Collecting local IPMI metrics is fairly straightforward. Simply configure your server to scrape the default metrics endpoint on the hosts running the exporter. ``` - job_name: ipmi scrape_interval: 1m scrape_timeout: 30s metrics_path: /metrics scheme: http static_configs: - targets: - 10.1.2.23:9290 - 10.1.2.24:9290 - 10.1.2.25:9290 ``` #### Remote metrics To add your IPMI targets to Prometheus, you can use any of the supported service discovery mechanism of your choice. The following example uses the file-based SD and should be easy to adjust to other scenarios. Create a YAML file that contains a list of targets, e.g.: ``` --- - targets: - 10.1.2.23 - 10.1.2.24 - 10.1.2.25 - 10.1.2.26 - 10.1.2.27 - 10.1.2.28 - 10.1.2.29 - 10.1.2.30 labels: job: ipmi_exporter ``` This file needs to be stored on the Prometheus server host. Assuming that this file is called `/srv/ipmi_exporter/targets.yml`, and the IPMI exporter is running on a host that has the DNS name `ipmi-exporter.internal.example.com`, add the following to your Prometheus config: ``` - job_name: ipmi params: module: default scrape_interval: 1m scrape_timeout: 30s metrics_path: /ipmi scheme: http file_sd_configs: - files: - /srv/ipmi_exporter/targets.yml refresh_interval: 5m relabel_configs: - source_labels: [__address__] separator: ; regex: (.*) target_label: __param_target replacement: ${1} action: replace - source_labels: [__param_target] separator: ; regex: (.*) target_label: instance replacement: ${1} action: replace - separator: ; regex: .* target_label: __address__ replacement: ipmi-exporter.internal.example.com:9290 action: replace ``` This assumes that all hosts use the default module. If you are using modules in the config file, like in the provided `ipmi_remote.yml` example config, you will need to specify on job for each module, using the respective group of targets. In a more extreme case, for example if you are using different passwords on every host, a good approach is to generate an exporter config file that uses the target name as module names, which would allow you to have single job that uses label replace to set the module. Leave out the `params` in the job definition and instead add a relabel rule like this one: ``` - source_labels: [__address__] separator: ; regex: (.*) target_label: __param_module replacement: ${1} action: replace ``` For more information, e.g. how to use mechanisms other than a file to discover the list of hosts to scrape, please refer to the [Prometheus documentation](https://prometheus.io/docs). ## Exported data ### 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 if fails, BMC info metrics (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. Example: ipmi_bmc_info{firmware_revision="2.52",manufacturer_id="Dell Inc. (674)"} 1 ### 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. ### Sensors These metric 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) 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 #### 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.1.0+ds/collector.go000066400000000000000000000344721362376477000214560ustar00rootroot00000000000000package main import ( "bytes" "crypto/rand" "encoding/csv" "encoding/hex" "fmt" "math" "os" "os/exec" "path" "path/filepath" "regexp" "strconv" "strings" "syscall" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/log" ) const ( namespace = "ipmi" targetLocal = "" ) var ( ipmiDCMICurrentPowerRegex = regexp.MustCompile(`^Current Power\s*:\s*(?P[0-9.]*)\s*Watts.*`) ipmiChassisPowerRegex = regexp.MustCompile(`^System Power\s*:\s(?P.*)`) bmcInfoFirmwareRevisionRegex = regexp.MustCompile(`^Firmware Revision\s*:\s*(?P[0-9.]*).*`) bmcInfoManufacturerIDRegex = regexp.MustCompile(`^Manufacturer ID\s*:\s*(?P.*)`) ) type collector struct { target string module string config *SafeConfig } type sensorData struct { ID int64 Name string Type string State string Value float64 Unit string Event string } type ipmiTarget struct { host string config IPMIConfig } 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, ) fanSpeedDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "fan_speed", "rpm"), "Fan speed in rotations per minute.", []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, ) powerConsumption = prometheus.NewDesc( prometheus.BuildFQName(namespace, "dcmi", "power_consumption_watts"), "Current power consumption in Watts.", []string{}, nil, ) chassisPowerState = prometheus.NewDesc( prometheus.BuildFQName(namespace, "chassis", "power_state"), "Current power state (1=on, 0=off).", []string{}, nil, ) bmcInfo = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc", "info"), "Constant metric with value '1' providing details about the BMC.", []string{"firmware_revision", "manufacturer_id"}, nil, ) 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, ) ) func pipeName() string { randBytes := make([]byte, 16) rand.Read(randBytes) return filepath.Join(os.TempDir(), "ipmi_exporter-"+hex.EncodeToString(randBytes)) } func freeipmiConfig(config IPMIConfig) string { var b strings.Builder if config.Driver != "" { fmt.Fprintf(&b, "driver-type %s\n", config.Driver) } if config.Privilege != "" { fmt.Fprintf(&b, "privilege-level %s\n", config.Privilege) } if config.User != "" { fmt.Fprintf(&b, "username %s\n", config.User) } if config.Password != "" { fmt.Fprintf(&b, "password %s\n", escapePassword(config.Password)) } if config.Timeout != 0 { fmt.Fprintf(&b, "session-timeout %d\n", config.Timeout) } if len(config.WorkaroundFlags) > 0 { fmt.Fprintf(&b, "workaround-flags") for _, flag := range config.WorkaroundFlags { fmt.Fprintf(&b, " %s", flag) } fmt.Fprintln(&b) } return b.String() } func freeipmiConfigPipe(config IPMIConfig) (string, error) { content := []byte(freeipmiConfig(config)) pipe := pipeName() err := syscall.Mkfifo(pipe, 0600) if err != nil { return "", err } go func(file string, data []byte) { f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModeNamedPipe) if err != nil { log.Errorf("Error opening pipe: %s", err) } if _, err := f.Write(data); err != nil { log.Errorf("Error writing config to pipe: %s", err) } f.Close() }(pipe, content) return pipe, nil } func freeipmiOutput(cmd string, target ipmiTarget, arg ...string) ([]byte, error) { pipe, err := freeipmiConfigPipe(target.config) if err != nil { return nil, err } defer os.Remove(pipe) args := []string{"--config-file", pipe} if !targetIsLocal(target.host) { args = append(args, "-h", target.host) } fqcmd := path.Join(*executablesPath, cmd) args = append(args, arg...) log.Debugf("Executing %s %v", fqcmd, args) out, err := exec.Command(fqcmd, args...).CombinedOutput() if err != nil { log.Errorf("Error while calling %s for %s: %s", cmd, targetName(target.host), out) } return out, err } func ipmiMonitoringOutput(target ipmiTarget) ([]byte, error) { return freeipmiOutput("ipmimonitoring", target, "-Q", "--ignore-unrecognized-events", "--comma-separated-output", "--no-header-output", "--sdr-cache-recreate") } func ipmiDCMIOutput(target ipmiTarget) ([]byte, error) { return freeipmiOutput("ipmi-dcmi", target, "--get-system-power-statistics") } func bmcInfoOutput(target ipmiTarget) ([]byte, error) { return freeipmiOutput("bmc-info", target, "--get-device-id") } func ipmiChassisOutput(target ipmiTarget) ([]byte, error) { return freeipmiOutput("ipmi-chassis", target, "--get-chassis-status") } func splitMonitoringOutput(impiOutput []byte, excludeSensorIds []int64) ([]sensorData, error) { var result []sensorData r := csv.NewReader(bytes.NewReader(impiOutput)) fields, err := r.ReadAll() if err != nil { return result, err } for _, line := range fields { var data sensorData data.ID, err = strconv.ParseInt(line[0], 10, 64) if err != nil { return result, err } if contains(excludeSensorIds, data.ID) { continue } data.Name = line[1] data.Type = line[2] data.State = line[3] value := line[4] if value != "N/A" { data.Value, err = strconv.ParseFloat(value, 64) if err != nil { return result, err } } else { data.Value = math.NaN() } data.Unit = line[5] data.Event = strings.Trim(line[6], "'") result = append(result, data) } return result, err } func getValue(ipmiOutput []byte, regex *regexp.Regexp) (string, error) { for _, line := range strings.Split(string(ipmiOutput), "\n") { match := regex.FindStringSubmatch(line) if match == nil { continue } for i, name := range regex.SubexpNames() { if name != "value" { continue } return match[i], nil } } return "", fmt.Errorf("Could not find value in output: %s", string(ipmiOutput)) } func getCurrentPowerConsumption(ipmiOutput []byte) (float64, error) { value, err := getValue(ipmiOutput, ipmiDCMICurrentPowerRegex) if err != nil { return -1, err } return strconv.ParseFloat(value, 64) } func getChassisPowerState(ipmiOutput []byte) (float64, error) { value, err := getValue(ipmiOutput, ipmiChassisPowerRegex) if err != nil { return -1, err } if value == "on" { return 1, err } return 0, err } func getBMCInfoFirmwareRevision(ipmiOutput []byte) (string, error) { return getValue(ipmiOutput, bmcInfoFirmwareRevisionRegex) } func getBMCInfoManufacturerID(ipmiOutput []byte) (string, error) { return getValue(ipmiOutput, bmcInfoManufacturerIDRegex) } // Describe implements Prometheus.Collector. func (c collector) Describe(ch chan<- *prometheus.Desc) { ch <- sensorStateDesc ch <- sensorValueDesc ch <- fanSpeedDesc ch <- temperatureDesc ch <- powerConsumption ch <- bmcInfo ch <- upDesc ch <- durationDesc } func collectTypedSensor(ch chan<- prometheus.Metric, desc, stateDesc *prometheus.Desc, state float64, data sensorData) { ch <- prometheus.MustNewConstMetric( desc, prometheus.GaugeValue, data.Value, 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 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, ) } func collectMonitoring(ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { output, err := ipmiMonitoringOutput(target) if err != nil { log.Errorf("Failed to collect ipmimonitoring data from %s: %s", targetName(target.host), err) return 0, err } excludeIds := target.config.ExcludeSensorIDs results, err := splitMonitoringOutput(output, excludeIds) if err != nil { log.Errorf("Failed to parse ipmimonitoring data from %s: %s", targetName(target.host), 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: log.Errorf("Unknown sensor state: '%s'\n", data.State) state = math.NaN() } log.Debugf("Got values: %v\n", data) switch data.Unit { case "RPM": collectTypedSensor(ch, fanSpeedDesc, fanSpeedStateDesc, state, data) case "C": collectTypedSensor(ch, temperatureDesc, temperatureStateDesc, state, data) case "A": collectTypedSensor(ch, currentDesc, currentStateDesc, state, data) case "V": collectTypedSensor(ch, voltageDesc, voltageStateDesc, state, data) case "W": collectTypedSensor(ch, powerDesc, powerStateDesc, state, data) default: collectGenericSensor(ch, state, data) } } return 1, nil } func collectDCMI(ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { output, err := ipmiDCMIOutput(target) if err != nil { log.Debugf("Failed to collect ipmi-dcmi data from %s: %s", targetName(target.host), err) return 0, err } currentPowerConsumption, err := getCurrentPowerConsumption(output) if err != nil { log.Errorf("Failed to parse ipmi-dcmi data from %s: %s", targetName(target.host), err) return 0, err } ch <- prometheus.MustNewConstMetric( powerConsumption, prometheus.GaugeValue, currentPowerConsumption, ) return 1, nil } func collectChassisState(ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { output, err := ipmiChassisOutput(target) if err != nil { log.Debugf("Failed to collect ipmi-chassis data from %s: %s", targetName(target.host), err) return 0, err } currentChassisPowerState, err := getChassisPowerState(output) if err != nil { log.Errorf("Failed to parse ipmi-chassis data from %s: %s", targetName(target.host), err) return 0, err } ch <- prometheus.MustNewConstMetric( chassisPowerState, prometheus.GaugeValue, currentChassisPowerState, ) return 1, nil } func collectBmcInfo(ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { output, err := bmcInfoOutput(target) if err != nil { log.Debugf("Failed to collect bmc-info data from %s: %s", targetName(target.host), err) return 0, err } firmwareRevision, err := getBMCInfoFirmwareRevision(output) if err != nil { log.Errorf("Failed to parse bmc-info data from %s: %s", targetName(target.host), err) return 0, err } manufacturerID, err := getBMCInfoManufacturerID(output) if err != nil { log.Errorf("Failed to parse bmc-info data from %s: %s", targetName(target.host), err) return 0, err } ch <- prometheus.MustNewConstMetric( bmcInfo, prometheus.GaugeValue, 1, firmwareRevision, manufacturerID, ) return 1, nil } 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 collector) Collect(ch chan<- prometheus.Metric) { start := time.Now() defer func() { duration := time.Since(start).Seconds() log.Debugf("Scrape of target %s took %f seconds.", targetName(c.target), 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.Collectors { var up int log.Debugf("Running collector: %s", collector) switch collector { case "ipmi": up, _ = collectMonitoring(ch, target) case "dcmi": up, _ = collectDCMI(ch, target) case "bmc": up, _ = collectBmcInfo(ch, target) case "chassis": up, _ = collectChassisState(ch, target) } markCollectorUp(ch, collector, up) } } func contains(s []int64, elm int64) bool { for _, a := range s { if a == elm { return true } } return false } func escapePassword(password string) string { return strings.Replace(password, "#", "\\#", -1) } func targetName(target string) string { if targetIsLocal(target) { return "[local]" } return target } func targetIsLocal(target string) bool { return target == targetLocal } prometheus-ipmi-exporter-1.1.0+ds/config.go000066400000000000000000000076701362376477000207350ustar00rootroot00000000000000package main import ( "fmt" "io/ioutil" "strings" "sync" "github.com/prometheus/common/log" yaml "gopkg.in/yaml.v2" ) // 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 []string `yaml:"collectors"` ExcludeSensorIDs []int64 `yaml:"exclude_sensor_ids"` WorkaroundFlags []string `yaml:"workaround_flags"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` } var emptyConfig = IPMIConfig{Collectors: []string{"ipmi", "dcmi", "bmc", "chassis"}} // CollectorName is used for unmarshaling the list of collectors in the yaml config file type CollectorName string 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 = emptyConfig 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 !(c == "ipmi" || c == "dcmi" || c == "bmc" || c == "chassis") { return fmt.Errorf("unknown collector name: %s", c) } } return nil } // 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 = ioutil.ReadFile(configFile) if err != nil { log.Errorf("Error reading config file: %s", 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 != "" { log.Infoln("Loaded config file", 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 { log.Errorf("Requested module %s for target %s not found, using default", module, 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 log.Debugf("Needed default config for target %s, but none configured, using FreeIPMI defaults", targetName(target)) config = emptyConfig } } return config } prometheus-ipmi-exporter-1.1.0+ds/docker-compose.yml000066400000000000000000000005261362376477000225670ustar00rootroot00000000000000version: '3.7' services: ipmi_exporter: build: context: . 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.1.0+ds/go.mod000066400000000000000000000010061362376477000202320ustar00rootroot00000000000000module github.com/soundcloud/ipmi_exporter go 1.12 require ( github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect github.com/golang/protobuf v1.3.3 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/prometheus/client_golang v1.4.1 github.com/prometheus/common v0.9.1 github.com/prometheus/procfs v0.0.9 // indirect golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/yaml.v2 v2.2.8 ) prometheus-ipmi-exporter-1.1.0+ds/go.sum000066400000000000000000000265161362376477000202740ustar00rootroot00000000000000github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.1 h1:FFSuS004yOQEtDdTq+TAOLP5xUq63KqAFYyOi8zA+Y8= github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.9 h1:DksSrntiTPE63NQuxGcFa1OS/odKfwJu3PJHrhKAy7Q= github.com/prometheus/procfs v0.0.9/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= prometheus-ipmi-exporter-1.1.0+ds/ipmi_local.yml000066400000000000000000000010541362376477000217620ustar00rootroot00000000000000# Configuration file for ipmi_exporter # This is an example config for scraping the local host. # In most cases, this should work without using a config file at all. modules: default: # Available collectors are bmc, ipmi, chassis, and dcmi collectors: - bmc - ipmi - dcmi - chassis # Got any sensors you don't care about? Add them here. exclude_sensor_ids: - 2 - 29 - 32 prometheus-ipmi-exporter-1.1.0+ds/ipmi_remote.yml000066400000000000000000000052231362376477000221650ustar00rootroot00000000000000# Configuration file for ipmi_exporter # This is an example config for scraping remote hosts via IPMI. # Information required to access remote IPMI interfaces can be supplied in the # 'modules' section. A scrape can request the usage of a given config by # setting the `module` URL parameter. modules: default: # These settings are used if no module is specified, the # specified module doesn't exist, or of course if # module=default is specified. user: "default_user" pass: "example_pw" # The below settings correspond to driver-type, privilege-level, and # session-timeout respectively, see `man 5 freeipmi.conf` (and e.g. # `man 8 ipmi-sensors` for a list of driver types). driver: "LAN_2_0" privilege: "user" # The session timeout is in milliseconds. Note that a scrape can take up # to (session-timeout * #-of-collectors) milliseconds, so set the scrape # timeout in Prometheus accordingly. timeout: 10000 # Available collectors are bmc, ipmi, chassis, and dcmi # If not specified, all three are used collectors: - bmc - ipmi - chassis # Got any sensors you don't care about? Add them here. exclude_sensor_ids: - 2 - 29 - 32 - 50 - 52 - 55 dcmi: # Use these settings when scraped with module=dcmi. user: "admin_user" pass: "another_pw" privilege: "admin" driver: "LAN_2_0" collectors: - dcmi thatspecialhost: # Use these settings when scraped with module=thatspecialhost. user: "some_user" pass: "secret_pw" privilege: "admin" driver: "LAN" collectors: - ipmi # Need any special workaround flags set? Add them here. # Workaround flags might be needed to address issues with specific vendor implementations # e.g. https://www.gnu.org/software/freeipmi/freeipmi-faq.html#Why-is-the-output-from-FreeIPMI-different-than-another-software_003f # For a full list of flags, refer to: # https://www.gnu.org/software/freeipmi/manpages/man8/ipmi-sensors.8.html#lbAL workaround_flags: - discretereading prometheus-ipmi-exporter-1.1.0+ds/main.go000066400000000000000000000100431362376477000204000ustar00rootroot00000000000000package main import ( "fmt" "net/http" "os" "os/signal" "syscall" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/log" "github.com/prometheus/common/version" kingpin "gopkg.in/alecthomas/kingpin.v2" ) var ( configFile = kingpin.Flag( "config.file", "Path to configuration file.", ).String() executablesPath = kingpin.Flag( "freeipmi.path", "Path to FreeIPMI executables (default: rely on $PATH).", ).String() listenAddress = kingpin.Flag( "web.listen-address", "Address to listen on for web interface and telemetry.", ).Default(":9290").String() sc = &SafeConfig{ C: &Config{}, } reloadCh chan chan error ) func remoteIPMIHandler(w http.ResponseWriter, r *http.Request) { target := r.URL.Query().Get("target") if target == "" { http.Error(w, "'target' parameter must be specified", 400) return } // Remote scrape will not work without some kind of config, so be pedantic about it module := r.URL.Query().Get("module") if module == "" { module = "default" } if !sc.HasModule(module) { http.Error(w, fmt.Sprintf("Unknown module %q", module), http.StatusBadRequest) return } log.Debugf("Scraping target '%s' with module '%s'", target, module) registry := prometheus.NewRegistry() remoteCollector := collector{target: target, module: module, config: sc} registry.MustRegister(remoteCollector) h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) h.ServeHTTP(w, r) } func updateConfiguration(w http.ResponseWriter, r *http.Request) { switch r.Method { case "POST": rc := make(chan error) reloadCh <- rc if err := <-rc; err != nil { http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError) } default: log.Errorf("Only POST requests allowed for %s", r.URL) w.Header().Set("Allow", "POST") http.Error(w, "Only POST requests allowed", http.StatusMethodNotAllowed) } } func main() { log.AddFlags(kingpin.CommandLine) kingpin.HelpFlag.Short('h') kingpin.Version(version.Print("ipmi_exporter")) kingpin.Parse() log.Infoln("Starting ipmi_exporter") // Bail early if the config is bad. if err := sc.ReloadConfig(*configFile); err != nil { log.Fatalf("Error parsing config file: %s", err) } hup := make(chan os.Signal) reloadCh = make(chan chan error) signal.Notify(hup, syscall.SIGHUP) go func() { for { select { case <-hup: if err := sc.ReloadConfig(*configFile); err != nil { log.Errorf("Error reloading config: %s", err) } case rc := <-reloadCh: if err := sc.ReloadConfig(*configFile); err != nil { log.Errorf("Error reloading config: %s", err) rc <- err } else { rc <- nil } } } }() localCollector := collector{target: targetLocal, module: "default", config: sc} prometheus.MustRegister(&localCollector) http.Handle("/metrics", promhttp.Handler()) // Regular metrics endpoint for local IPMI metrics. http.HandleFunc("/ipmi", remoteIPMIHandler) // Endpoint to do IPMI scrapes. http.HandleFunc("/-/reload", updateConfiguration) // Endpoint to reload configuration. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` IPMI Exporter

IPMI Exporter


Local metrics

Config

`)) }) log.Infof("Listening on %s", *listenAddress) err := http.ListenAndServe(*listenAddress, nil) if err != nil { log.Fatal(err) } }