├── .github └── workflows │ ├── docker_latest.yml │ ├── docker_tag.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .goreleaser ├── CODEOWNERS ├── Dockerfile ├── LICENSE ├── LICENSES └── MIT.txt ├── README.md ├── go.mod ├── go.sum ├── iLO-grafana-dashboard.json ├── ilo-grafana-alerts.yaml ├── main.go ├── pkg ├── chassis │ ├── collector.go │ ├── power │ │ ├── metrics.go │ │ ├── power.go │ │ └── power_supply.go │ └── thermal │ │ ├── fan.go │ │ ├── metrics.go │ │ ├── temperature.go │ │ └── thermal.go ├── client │ ├── api_client.go │ ├── client.go │ └── dunmy_client.go ├── common │ ├── collector_context.go │ ├── member_list.go │ └── status.go ├── manager │ ├── collector.go │ └── manager.go └── system │ ├── collector.go │ ├── memory │ ├── memory_dimm.go │ └── metrics.go │ ├── processor │ ├── metrics.go │ └── processor.go │ ├── storage │ ├── disk_drive.go │ ├── location.go │ ├── metrics.go │ └── storage_info.go │ └── system.go └── tracing.go /.github/workflows/docker_latest.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_run: 3 | workflows: ["Test"] 4 | branches: [main] 5 | types: 6 | - completed 7 | 8 | name: Docker Build (latest) 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 13 | 14 | steps: 15 | - name: Check Out Repo 16 | uses: actions/checkout@v2 17 | 18 | - name: Login to Docker Hub 19 | uses: docker/login-action@v1 20 | with: 21 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 22 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 23 | 24 | - name: Set up Docker Buildx 25 | id: buildx 26 | uses: docker/setup-buildx-action@v1 27 | 28 | - name: Extract repo name 29 | id: extract_repo_name 30 | shell: bash 31 | run: echo "##[set-output name=repo;]$(echo ${GITHUB_REPOSITORY#*/})" 32 | 33 | - name: Build and push 34 | id: docker_build 35 | uses: docker/build-push-action@v2 36 | with: 37 | context: ./ 38 | file: ./Dockerfile 39 | push: true 40 | tags: mauvesoftware/${{ steps.extract_repo_name.outputs.repo }}:latest 41 | 42 | - name: Image digest 43 | run: echo ${{ steps.docker_build.outputs.digest }} 44 | -------------------------------------------------------------------------------- /.github/workflows/docker_tag.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '*.*.*' 5 | 6 | name: Docker Build (tag) 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Check Out Repo 13 | uses: actions/checkout@v2 14 | 15 | - name: Login to Docker Hub 16 | uses: docker/login-action@v1 17 | with: 18 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 19 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 20 | 21 | - name: Set up Docker Buildx 22 | id: buildx 23 | uses: docker/setup-buildx-action@v1 24 | 25 | - name: Extract repo name 26 | id: extract_repo_name 27 | shell: bash 28 | run: echo "##[set-output name=repo;]$(echo ${GITHUB_REPOSITORY#*/})" 29 | 30 | - name: Extract tag name 31 | id: extract_tag_name 32 | shell: bash 33 | run: echo "##[set-output name=tag;]$(echo ${GITHUB_REF##*/})" 34 | 35 | - name: Build and push 36 | id: docker_build_tag 37 | uses: docker/build-push-action@v2 38 | with: 39 | context: ./ 40 | file: ./Dockerfile 41 | push: true 42 | tags: mauvesoftware/${{ steps.extract_repo_name.outputs.repo }}:v${{ steps.extract_tag_name.outputs.tag }} 43 | 44 | - name: Image digest 45 | run: echo ${{ steps.docker_build_tag.outputs.digest }} 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '*.*.*' 5 | 6 | name: Release 7 | jobs: 8 | goreleaser: 9 | name: Create Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: "1.24" 19 | 20 | - name: Run GoReleaser 21 | uses: goreleaser/goreleaser-action@v2 22 | with: 23 | args: release 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | workflow_dispatch: 9 | 10 | name: Test 11 | jobs: 12 | test: 13 | strategy: 14 | matrix: 15 | go-version: [1.24.x] 16 | platform: [ubuntu-latest, macos-latest, windows-latest] 17 | runs-on: ${{ matrix.platform }} 18 | steps: 19 | - name: Install Go 20 | if: success() 21 | uses: actions/setup-go@v2 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | - name: Checkout code 25 | uses: actions/checkout@v2 26 | - name: Build 27 | run: go build 28 | - name: Run tests 29 | run: go test ./... -v -covermode=count 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ilo_exporter 2 | 3 | .idea/* 4 | .vscode/* 5 | *.iml 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.goreleaser: -------------------------------------------------------------------------------- 1 | dist: artifacts 2 | before: 3 | hooks: 4 | - go mod download 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - linux 10 | - darwin 11 | - freebsd 12 | goarch: 13 | - amd64 14 | - arm 15 | - arm64 16 | ignore: 17 | - goos: freebsd 18 | goarch: arm64 19 | ldflags: -s -w -X main.version={{.Version}} 20 | binary: ilo5_exporter 21 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @czerwonk 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang as builder 2 | ADD . /go/ilo_exporter/ 3 | WORKDIR /go/ilo_exporter 4 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /go/bin/ilo_exporter 5 | 6 | FROM alpine:latest 7 | ENV API_USERNAME '' 8 | ENV API_PASSWORD '' 9 | ENV API_MAX_CONCURRENT '4' 10 | ENV CMD_FLAGS '' 11 | RUN apk --no-cache add ca-certificates bash 12 | COPY --from=builder /go/bin/ilo_exporter /app/ilo_exporter 13 | EXPOSE 19545 14 | ENTRYPOINT /app/ilo_exporter -api.username=$API_USERNAME -api.password=$API_PASSWORD -api.max-concurrent-requests=$API_MAX_CONCURRENT $CMD_FLAGS 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mauve Mailorder Software GmbH & Co. KG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Report Card](https://goreportcard.com/badge/github.com/mauvesoftware/ilo_exporter)](https://goreportcard.com/report/github.com/mauvesoftware/ilo_exporter) 2 | # ilo_exporter 3 | Metrics exporter for HP iLO to prometheus 4 | 5 | ## Breaking changes 6 | Beginning with version 1.0.0 the projects ilo4_exporter and ilo5_exporter were merged to ilo_exporter. Due to this change metric names are based on ilo5_exporter but were renamed to show compatiblity to both versions. 7 | 8 | ## Install 9 | ``` 10 | go get -u github.com/MauveSoftware/ilo_exporter 11 | ``` 12 | 13 | ## Usage 14 | Running the exporter with the following test credentials: 15 | 16 | ``` 17 | Username: ilo_exporter 18 | Password: g3tM3trics 19 | ``` 20 | 21 | ### Binary 22 | ```bash 23 | ./ilo_exporter -api.username=ilo_exporter -api.password=g3tM3trics 24 | ``` 25 | 26 | ### Docker 27 | ```bash 28 | docker run -d --restart always --name ilo_exporter -p 19545:19545 -e API_USERNAME=ilo_exporter -e API_PASSWORD=g3tM3trics mauvesoftware/ilo_exporter 29 | ``` 30 | 31 | ## Prometheus configuration 32 | To get metrics for 172.16.0.200 using https://my-exporter-tld/metrics?hosts=172.16.0.200 33 | 34 | ```bash 35 | - job_name: 'ilo' 36 | scrape_interval: 300s 37 | scrape_timeout: 120s 38 | scheme: https 39 | static_configs: 40 | - targets: 41 | - 172.16.0.200 42 | relabel_configs: 43 | - source_labels: [__address__] 44 | target_label: __param_host 45 | - source_labels: [__param_host] 46 | target_label: instance 47 | replacement: '${1}' 48 | - target_label: __address__ 49 | replacement: my-exporter-tld 50 | ``` 51 | 52 | ## Grafana 53 | 54 | For users of [Grafana](https://grafana.com/), this repository includes an example [dashboard](iLO-grafana-dashboard.json) and example [alert rules](ilo-grafana-alerts.yaml). 55 | 56 | ## License 57 | (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 58 | 59 | ## Prometheus 60 | see https://prometheus.io/ 61 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/MauveSoftware/ilo_exporter 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/prometheus/client_golang v1.22.0 7 | github.com/sirupsen/logrus v1.9.3 8 | go.opentelemetry.io/otel v1.35.0 9 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 10 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 11 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 12 | go.opentelemetry.io/otel/sdk v1.35.0 13 | go.opentelemetry.io/otel/trace v1.35.0 14 | golang.org/x/sync v0.14.0 15 | ) 16 | 17 | require ( 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 20 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 21 | github.com/go-logr/logr v1.4.2 // indirect 22 | github.com/go-logr/stdr v1.2.2 // indirect 23 | github.com/google/uuid v1.6.0 // indirect 24 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect 25 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 26 | github.com/prometheus/client_model v0.6.2 // indirect 27 | github.com/prometheus/common v0.64.0 // indirect 28 | github.com/prometheus/procfs v0.16.1 // indirect 29 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 30 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 31 | go.opentelemetry.io/proto/otlp v1.6.0 // indirect 32 | golang.org/x/net v0.40.0 // indirect 33 | golang.org/x/sys v0.33.0 // indirect 34 | golang.org/x/text v0.25.0 // indirect 35 | google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect 36 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect 37 | google.golang.org/grpc v1.72.1 // indirect 38 | google.golang.org/protobuf v1.36.6 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 4 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 5 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 6 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 11 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 12 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 13 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 14 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 15 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 16 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 17 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 18 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 19 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 20 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 21 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= 22 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= 23 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 24 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 25 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 26 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 27 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 28 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 32 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 33 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 34 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 35 | github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= 36 | github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= 37 | github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 38 | github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 39 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 40 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 41 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 42 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 43 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 44 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 45 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 46 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 47 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 48 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 49 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= 50 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= 51 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= 52 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= 53 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE= 54 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg= 55 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 56 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 57 | go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= 58 | go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= 59 | go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 60 | go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= 61 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 62 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 63 | go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= 64 | go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= 65 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 66 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 67 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 68 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 69 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 70 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 71 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 72 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 73 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 74 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 75 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 76 | google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= 77 | google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= 78 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= 79 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 80 | google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= 81 | google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 82 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 83 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 84 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 85 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 86 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 87 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 88 | -------------------------------------------------------------------------------- /iLO-grafana-dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__elements": {}, 13 | "__requires": [ 14 | { 15 | "type": "panel", 16 | "id": "alertlist", 17 | "name": "Alert list", 18 | "version": "" 19 | }, 20 | { 21 | "type": "panel", 22 | "id": "gauge", 23 | "name": "Gauge", 24 | "version": "" 25 | }, 26 | { 27 | "type": "grafana", 28 | "id": "grafana", 29 | "name": "Grafana", 30 | "version": "10.0.1" 31 | }, 32 | { 33 | "type": "datasource", 34 | "id": "prometheus", 35 | "name": "Prometheus", 36 | "version": "1.0.0" 37 | }, 38 | { 39 | "type": "panel", 40 | "id": "stat", 41 | "name": "Stat", 42 | "version": "" 43 | }, 44 | { 45 | "type": "panel", 46 | "id": "timeseries", 47 | "name": "Time series", 48 | "version": "" 49 | } 50 | ], 51 | "annotations": { 52 | "list": [ 53 | { 54 | "builtIn": 1, 55 | "datasource": { 56 | "type": "datasource", 57 | "uid": "grafana" 58 | }, 59 | "enable": true, 60 | "hide": true, 61 | "iconColor": "rgba(0, 211, 255, 1)", 62 | "name": "Annotations & Alerts", 63 | "target": { 64 | "limit": 100, 65 | "matchAny": false, 66 | "tags": [], 67 | "type": "dashboard" 68 | }, 69 | "type": "dashboard" 70 | } 71 | ] 72 | }, 73 | "description": "iLO data from https://github.com/MauveSoftware/ilo_exporter", 74 | "editable": true, 75 | "fiscalYearStartMonth": 0, 76 | "gnetId": 13177, 77 | "graphTooltip": 0, 78 | "id": null, 79 | "links": [], 80 | "liveNow": false, 81 | "panels": [ 82 | { 83 | "datasource": { 84 | "type": "prometheus", 85 | "uid": "${DS_PROMETHEUS}" 86 | }, 87 | "fieldConfig": { 88 | "defaults": { 89 | "mappings": [ 90 | { 91 | "options": { 92 | "0": { 93 | "color": "red", 94 | "index": 1, 95 | "text": "Off" 96 | }, 97 | "1": { 98 | "color": "green", 99 | "index": 0, 100 | "text": "On" 101 | } 102 | }, 103 | "type": "value" 104 | } 105 | ], 106 | "thresholds": { 107 | "mode": "absolute", 108 | "steps": [ 109 | { 110 | "color": "red", 111 | "value": null 112 | }, 113 | { 114 | "color": "green", 115 | "value": 1 116 | } 117 | ] 118 | }, 119 | "unit": "none" 120 | }, 121 | "overrides": [] 122 | }, 123 | "gridPos": { 124 | "h": 3, 125 | "w": 3, 126 | "x": 0, 127 | "y": 0 128 | }, 129 | "id": 8, 130 | "options": { 131 | "colorMode": "value", 132 | "graphMode": "area", 133 | "justifyMode": "auto", 134 | "orientation": "auto", 135 | "reduceOptions": { 136 | "calcs": [ 137 | "mean" 138 | ], 139 | "fields": "", 140 | "values": false 141 | }, 142 | "textMode": "auto" 143 | }, 144 | "pluginVersion": "10.0.1", 145 | "targets": [ 146 | { 147 | "datasource": { 148 | "type": "prometheus", 149 | "uid": "${DS_PROMETHEUS}" 150 | }, 151 | "editorMode": "code", 152 | "expr": "ilo_power_up{host='$node'}", 153 | "interval": "", 154 | "legendFormat": "{{sensor}}", 155 | "range": true, 156 | "refId": "A" 157 | } 158 | ], 159 | "title": "Power Status", 160 | "type": "stat" 161 | }, 162 | { 163 | "datasource": { 164 | "type": "prometheus", 165 | "uid": "${DS_PROMETHEUS}" 166 | }, 167 | "fieldConfig": { 168 | "defaults": { 169 | "mappings": [], 170 | "thresholds": { 171 | "mode": "absolute", 172 | "steps": [ 173 | { 174 | "color": "green", 175 | "value": null 176 | }, 177 | { 178 | "color": "red", 179 | "value": 80 180 | } 181 | ] 182 | }, 183 | "unit": "watt" 184 | }, 185 | "overrides": [] 186 | }, 187 | "gridPos": { 188 | "h": 3, 189 | "w": 3, 190 | "x": 3, 191 | "y": 0 192 | }, 193 | "id": 14, 194 | "options": { 195 | "colorMode": "value", 196 | "graphMode": "area", 197 | "justifyMode": "auto", 198 | "orientation": "auto", 199 | "reduceOptions": { 200 | "calcs": [ 201 | "mean" 202 | ], 203 | "fields": "", 204 | "values": false 205 | }, 206 | "textMode": "auto" 207 | }, 208 | "pluginVersion": "10.0.1", 209 | "targets": [ 210 | { 211 | "datasource": { 212 | "type": "prometheus", 213 | "uid": "${DS_PROMETHEUS}" 214 | }, 215 | "editorMode": "code", 216 | "expr": "ilo_power_current_watt{host='$node'}", 217 | "interval": "", 218 | "legendFormat": "{{sensor}}", 219 | "range": true, 220 | "refId": "A" 221 | } 222 | ], 223 | "title": "Power", 224 | "type": "stat" 225 | }, 226 | { 227 | "datasource": { 228 | "type": "prometheus", 229 | "uid": "${DS_PROMETHEUS}" 230 | }, 231 | "fieldConfig": { 232 | "defaults": { 233 | "mappings": [], 234 | "thresholds": { 235 | "mode": "absolute", 236 | "steps": [ 237 | { 238 | "color": "green", 239 | "value": null 240 | }, 241 | { 242 | "color": "red", 243 | "value": 80 244 | } 245 | ] 246 | }, 247 | "unit": "watt" 248 | }, 249 | "overrides": [] 250 | }, 251 | "gridPos": { 252 | "h": 3, 253 | "w": 3, 254 | "x": 6, 255 | "y": 0 256 | }, 257 | "id": 13, 258 | "options": { 259 | "colorMode": "value", 260 | "graphMode": "area", 261 | "justifyMode": "auto", 262 | "orientation": "auto", 263 | "reduceOptions": { 264 | "calcs": [ 265 | "mean" 266 | ], 267 | "fields": "", 268 | "values": false 269 | }, 270 | "textMode": "auto" 271 | }, 272 | "pluginVersion": "10.0.1", 273 | "targets": [ 274 | { 275 | "datasource": { 276 | "type": "prometheus", 277 | "uid": "${DS_PROMETHEUS}" 278 | }, 279 | "editorMode": "code", 280 | "expr": "ilo_power_current_watt{host='$node'} * 30 * 24 ", 281 | "interval": "", 282 | "legendFormat": "", 283 | "range": true, 284 | "refId": "A" 285 | } 286 | ], 287 | "title": "Power usage 30d", 288 | "type": "stat" 289 | }, 290 | { 291 | "datasource": { 292 | "type": "prometheus", 293 | "uid": "${DS_PROMETHEUS}" 294 | }, 295 | "description": "", 296 | "fieldConfig": { 297 | "defaults": { 298 | "mappings": [], 299 | "thresholds": { 300 | "mode": "absolute", 301 | "steps": [ 302 | { 303 | "color": "green", 304 | "value": null 305 | }, 306 | { 307 | "color": "red", 308 | "value": 1 309 | } 310 | ] 311 | }, 312 | "unit": "none" 313 | }, 314 | "overrides": [] 315 | }, 316 | "gridPos": { 317 | "h": 3, 318 | "w": 3, 319 | "x": 9, 320 | "y": 0 321 | }, 322 | "id": 15, 323 | "options": { 324 | "colorMode": "value", 325 | "graphMode": "area", 326 | "justifyMode": "auto", 327 | "orientation": "auto", 328 | "reduceOptions": { 329 | "calcs": [ 330 | "mean" 331 | ], 332 | "fields": "", 333 | "values": false 334 | }, 335 | "textMode": "auto" 336 | }, 337 | "pluginVersion": "10.0.1", 338 | "targets": [ 339 | { 340 | "datasource": { 341 | "type": "prometheus", 342 | "uid": "${DS_PROMETHEUS}" 343 | }, 344 | "editorMode": "code", 345 | "expr": "count(ilo_memory_dimm_healthy{host='$node'}) - count(ilo_memory_dimm_healthy{host='$node'} == 1)", 346 | "interval": "", 347 | "legendFormat": "", 348 | "range": true, 349 | "refId": "A" 350 | } 351 | ], 352 | "title": "Unhealthy DIMMs", 353 | "type": "stat" 354 | }, 355 | { 356 | "datasource": { 357 | "type": "prometheus", 358 | "uid": "${DS_PROMETHEUS}" 359 | }, 360 | "description": "", 361 | "fieldConfig": { 362 | "defaults": { 363 | "mappings": [], 364 | "thresholds": { 365 | "mode": "absolute", 366 | "steps": [ 367 | { 368 | "color": "green", 369 | "value": null 370 | }, 371 | { 372 | "color": "red", 373 | "value": 1 374 | } 375 | ] 376 | }, 377 | "unit": "none" 378 | }, 379 | "overrides": [] 380 | }, 381 | "gridPos": { 382 | "h": 3, 383 | "w": 3, 384 | "x": 12, 385 | "y": 0 386 | }, 387 | "id": 16, 388 | "options": { 389 | "colorMode": "value", 390 | "graphMode": "area", 391 | "justifyMode": "auto", 392 | "orientation": "auto", 393 | "reduceOptions": { 394 | "calcs": [ 395 | "mean" 396 | ], 397 | "fields": "", 398 | "values": false 399 | }, 400 | "textMode": "auto" 401 | }, 402 | "pluginVersion": "10.0.1", 403 | "targets": [ 404 | { 405 | "datasource": { 406 | "type": "prometheus", 407 | "uid": "${DS_PROMETHEUS}" 408 | }, 409 | "editorMode": "code", 410 | "expr": "count(ilo_power_supply_enabled{host='$node'}) - count(ilo_power_supply_enabled{host='$node'} == 1)", 411 | "interval": "", 412 | "legendFormat": "", 413 | "range": true, 414 | "refId": "A" 415 | } 416 | ], 417 | "title": "Disabled PSUs", 418 | "type": "stat" 419 | }, 420 | { 421 | "datasource": { 422 | "type": "prometheus", 423 | "uid": "${DS_PROMETHEUS}" 424 | }, 425 | "description": "", 426 | "fieldConfig": { 427 | "defaults": { 428 | "mappings": [], 429 | "thresholds": { 430 | "mode": "absolute", 431 | "steps": [ 432 | { 433 | "color": "green", 434 | "value": null 435 | }, 436 | { 437 | "color": "red", 438 | "value": 1 439 | } 440 | ] 441 | }, 442 | "unit": "none" 443 | }, 444 | "overrides": [] 445 | }, 446 | "gridPos": { 447 | "h": 3, 448 | "w": 3, 449 | "x": 15, 450 | "y": 0 451 | }, 452 | "id": 17, 453 | "options": { 454 | "colorMode": "value", 455 | "graphMode": "area", 456 | "justifyMode": "auto", 457 | "orientation": "auto", 458 | "reduceOptions": { 459 | "calcs": [ 460 | "mean" 461 | ], 462 | "fields": "", 463 | "values": false 464 | }, 465 | "textMode": "auto" 466 | }, 467 | "pluginVersion": "10.0.1", 468 | "targets": [ 469 | { 470 | "datasource": { 471 | "type": "prometheus", 472 | "uid": "${DS_PROMETHEUS}" 473 | }, 474 | "editorMode": "code", 475 | "expr": "count(ilo_power_supply_healthy{host='$node'}) - count(ilo_power_supply_healthy{host='$node'} == 1)", 476 | "interval": "", 477 | "legendFormat": "", 478 | "range": true, 479 | "refId": "A" 480 | } 481 | ], 482 | "title": "Unhealthy PSUs", 483 | "type": "stat" 484 | }, 485 | { 486 | "datasource": { 487 | "type": "prometheus", 488 | "uid": "${DS_PROMETHEUS}" 489 | }, 490 | "description": "", 491 | "fieldConfig": { 492 | "defaults": { 493 | "mappings": [], 494 | "thresholds": { 495 | "mode": "absolute", 496 | "steps": [ 497 | { 498 | "color": "green", 499 | "value": null 500 | }, 501 | { 502 | "color": "red", 503 | "value": 1 504 | } 505 | ] 506 | }, 507 | "unit": "none" 508 | }, 509 | "overrides": [] 510 | }, 511 | "gridPos": { 512 | "h": 3, 513 | "w": 3, 514 | "x": 18, 515 | "y": 0 516 | }, 517 | "id": 18, 518 | "options": { 519 | "colorMode": "value", 520 | "graphMode": "area", 521 | "justifyMode": "auto", 522 | "orientation": "auto", 523 | "reduceOptions": { 524 | "calcs": [ 525 | "mean" 526 | ], 527 | "fields": "", 528 | "values": false 529 | }, 530 | "textMode": "auto" 531 | }, 532 | "pluginVersion": "10.0.1", 533 | "targets": [ 534 | { 535 | "datasource": { 536 | "type": "prometheus", 537 | "uid": "${DS_PROMETHEUS}" 538 | }, 539 | "editorMode": "code", 540 | "expr": "count(ilo_processor_healthy{host='$node'}) - count(ilo_processor_healthy{host='$node'} == 1)", 541 | "interval": "", 542 | "legendFormat": "", 543 | "range": true, 544 | "refId": "A" 545 | } 546 | ], 547 | "title": "Unhealthy Processors", 548 | "type": "stat" 549 | }, 550 | { 551 | "datasource": { 552 | "type": "prometheus", 553 | "uid": "${DS_PROMETHEUS}" 554 | }, 555 | "description": "", 556 | "fieldConfig": { 557 | "defaults": { 558 | "mappings": [], 559 | "thresholds": { 560 | "mode": "absolute", 561 | "steps": [ 562 | { 563 | "color": "green", 564 | "value": null 565 | }, 566 | { 567 | "color": "red", 568 | "value": 1 569 | } 570 | ] 571 | }, 572 | "unit": "none" 573 | }, 574 | "overrides": [] 575 | }, 576 | "gridPos": { 577 | "h": 3, 578 | "w": 3, 579 | "x": 21, 580 | "y": 0 581 | }, 582 | "id": 19, 583 | "options": { 584 | "colorMode": "value", 585 | "graphMode": "area", 586 | "justifyMode": "auto", 587 | "orientation": "auto", 588 | "reduceOptions": { 589 | "calcs": [ 590 | "mean" 591 | ], 592 | "fields": "", 593 | "values": false 594 | }, 595 | "textMode": "auto" 596 | }, 597 | "pluginVersion": "10.0.1", 598 | "targets": [ 599 | { 600 | "datasource": { 601 | "type": "prometheus", 602 | "uid": "${DS_PROMETHEUS}" 603 | }, 604 | "editorMode": "code", 605 | "expr": "count(ilo_storage_disk_healthy{host='$node'}) - count(ilo_storage_disk_healthy{host='$node'} == 1)", 606 | "interval": "", 607 | "legendFormat": "", 608 | "range": true, 609 | "refId": "A" 610 | } 611 | ], 612 | "title": "Unhealthy Disks", 613 | "type": "stat" 614 | }, 615 | { 616 | "datasource": { 617 | "type": "prometheus", 618 | "uid": "${DS_PROMETHEUS}" 619 | }, 620 | "fieldConfig": { 621 | "defaults": { 622 | "mappings": [], 623 | "thresholds": { 624 | "mode": "absolute", 625 | "steps": [ 626 | { 627 | "color": "green", 628 | "value": null 629 | }, 630 | { 631 | "color": "red", 632 | "value": 70 633 | } 634 | ] 635 | }, 636 | "unit": "celsius" 637 | }, 638 | "overrides": [] 639 | }, 640 | "gridPos": { 641 | "h": 7, 642 | "w": 12, 643 | "x": 0, 644 | "y": 3 645 | }, 646 | "id": 10, 647 | "options": { 648 | "orientation": "auto", 649 | "reduceOptions": { 650 | "calcs": [ 651 | "mean" 652 | ], 653 | "fields": "", 654 | "values": false 655 | }, 656 | "showThresholdLabels": false, 657 | "showThresholdMarkers": false 658 | }, 659 | "pluginVersion": "10.0.1", 660 | "targets": [ 661 | { 662 | "datasource": { 663 | "type": "prometheus", 664 | "uid": "${DS_PROMETHEUS}" 665 | }, 666 | "editorMode": "code", 667 | "expr": "ilo_chassis_temperature_current{host='$node'}", 668 | "interval": "", 669 | "legendFormat": "{{name}}", 670 | "range": true, 671 | "refId": "A" 672 | } 673 | ], 674 | "title": "Temperatures", 675 | "type": "gauge" 676 | }, 677 | { 678 | "datasource": { 679 | "type": "prometheus", 680 | "uid": "${DS_PROMETHEUS}" 681 | }, 682 | "description": "", 683 | "fieldConfig": { 684 | "defaults": { 685 | "color": { 686 | "mode": "palette-classic" 687 | }, 688 | "custom": { 689 | "axisCenteredZero": false, 690 | "axisColorMode": "text", 691 | "axisLabel": "", 692 | "axisPlacement": "auto", 693 | "barAlignment": 0, 694 | "drawStyle": "line", 695 | "fillOpacity": 0, 696 | "gradientMode": "none", 697 | "hideFrom": { 698 | "legend": false, 699 | "tooltip": false, 700 | "viz": false 701 | }, 702 | "lineInterpolation": "linear", 703 | "lineWidth": 1, 704 | "pointSize": 1, 705 | "scaleDistribution": { 706 | "type": "linear" 707 | }, 708 | "showPoints": "always", 709 | "spanNulls": false, 710 | "stacking": { 711 | "group": "A", 712 | "mode": "none" 713 | }, 714 | "thresholdsStyle": { 715 | "mode": "off" 716 | } 717 | }, 718 | "mappings": [], 719 | "thresholds": { 720 | "mode": "absolute", 721 | "steps": [ 722 | { 723 | "color": "green", 724 | "value": null 725 | }, 726 | { 727 | "color": "red", 728 | "value": 80 729 | } 730 | ] 731 | }, 732 | "unit": "celsius" 733 | }, 734 | "overrides": [] 735 | }, 736 | "gridPos": { 737 | "h": 7, 738 | "w": 12, 739 | "x": 12, 740 | "y": 3 741 | }, 742 | "id": 2, 743 | "options": { 744 | "legend": { 745 | "calcs": [], 746 | "displayMode": "list", 747 | "placement": "bottom", 748 | "showLegend": true 749 | }, 750 | "tooltip": { 751 | "mode": "multi", 752 | "sort": "desc" 753 | } 754 | }, 755 | "pluginVersion": "9.0.5", 756 | "targets": [ 757 | { 758 | "datasource": { 759 | "type": "prometheus", 760 | "uid": "${DS_PROMETHEUS}" 761 | }, 762 | "editorMode": "code", 763 | "expr": "ilo_chassis_temperature_current{host='$node'}", 764 | "interval": "", 765 | "legendFormat": "{{name}}", 766 | "range": true, 767 | "refId": "A" 768 | } 769 | ], 770 | "title": "Temperatures", 771 | "type": "timeseries" 772 | }, 773 | { 774 | "datasource": { 775 | "type": "prometheus", 776 | "uid": "${DS_PROMETHEUS}" 777 | }, 778 | "description": "", 779 | "fieldConfig": { 780 | "defaults": { 781 | "color": { 782 | "mode": "palette-classic" 783 | }, 784 | "custom": { 785 | "axisCenteredZero": false, 786 | "axisColorMode": "text", 787 | "axisLabel": "", 788 | "axisPlacement": "auto", 789 | "barAlignment": 0, 790 | "drawStyle": "line", 791 | "fillOpacity": 0, 792 | "gradientMode": "none", 793 | "hideFrom": { 794 | "legend": false, 795 | "tooltip": false, 796 | "viz": false 797 | }, 798 | "lineInterpolation": "linear", 799 | "lineWidth": 1, 800 | "pointSize": 1, 801 | "scaleDistribution": { 802 | "type": "linear" 803 | }, 804 | "showPoints": "always", 805 | "spanNulls": false, 806 | "stacking": { 807 | "group": "A", 808 | "mode": "none" 809 | }, 810 | "thresholdsStyle": { 811 | "mode": "off" 812 | } 813 | }, 814 | "mappings": [], 815 | "thresholds": { 816 | "mode": "absolute", 817 | "steps": [ 818 | { 819 | "color": "green", 820 | "value": null 821 | }, 822 | { 823 | "color": "red", 824 | "value": 80 825 | } 826 | ] 827 | }, 828 | "unit": "percent" 829 | }, 830 | "overrides": [] 831 | }, 832 | "gridPos": { 833 | "h": 7, 834 | "w": 12, 835 | "x": 0, 836 | "y": 10 837 | }, 838 | "id": 3, 839 | "options": { 840 | "legend": { 841 | "calcs": [], 842 | "displayMode": "list", 843 | "placement": "bottom", 844 | "showLegend": true 845 | }, 846 | "tooltip": { 847 | "mode": "multi", 848 | "sort": "desc" 849 | } 850 | }, 851 | "pluginVersion": "9.0.5", 852 | "targets": [ 853 | { 854 | "datasource": { 855 | "type": "prometheus", 856 | "uid": "${DS_PROMETHEUS}" 857 | }, 858 | "editorMode": "code", 859 | "expr": "ilo_chassis_fan_current_percent{host='$node'}", 860 | "interval": "", 861 | "legendFormat": "{{name}}", 862 | "range": true, 863 | "refId": "A" 864 | } 865 | ], 866 | "title": "Fans Speed (%)", 867 | "type": "timeseries" 868 | }, 869 | { 870 | "datasource": { 871 | "type": "prometheus", 872 | "uid": "${DS_PROMETHEUS}" 873 | }, 874 | "description": "", 875 | "fieldConfig": { 876 | "defaults": { 877 | "color": { 878 | "mode": "palette-classic" 879 | }, 880 | "custom": { 881 | "axisCenteredZero": false, 882 | "axisColorMode": "text", 883 | "axisLabel": "", 884 | "axisPlacement": "auto", 885 | "barAlignment": 0, 886 | "drawStyle": "line", 887 | "fillOpacity": 1, 888 | "gradientMode": "none", 889 | "hideFrom": { 890 | "legend": false, 891 | "tooltip": false, 892 | "viz": false 893 | }, 894 | "lineInterpolation": "linear", 895 | "lineWidth": 1, 896 | "pointSize": 1, 897 | "scaleDistribution": { 898 | "type": "linear" 899 | }, 900 | "showPoints": "always", 901 | "spanNulls": false, 902 | "stacking": { 903 | "group": "A", 904 | "mode": "none" 905 | }, 906 | "thresholdsStyle": { 907 | "mode": "off" 908 | } 909 | }, 910 | "mappings": [], 911 | "thresholds": { 912 | "mode": "absolute", 913 | "steps": [ 914 | { 915 | "color": "green", 916 | "value": null 917 | }, 918 | { 919 | "color": "red", 920 | "value": 80 921 | } 922 | ] 923 | }, 924 | "unit": "watt" 925 | }, 926 | "overrides": [] 927 | }, 928 | "gridPos": { 929 | "h": 7, 930 | "w": 12, 931 | "x": 12, 932 | "y": 10 933 | }, 934 | "id": 4, 935 | "options": { 936 | "legend": { 937 | "calcs": [], 938 | "displayMode": "list", 939 | "placement": "bottom", 940 | "showLegend": true 941 | }, 942 | "tooltip": { 943 | "mode": "multi", 944 | "sort": "desc" 945 | } 946 | }, 947 | "pluginVersion": "9.0.5", 948 | "targets": [ 949 | { 950 | "datasource": { 951 | "type": "prometheus", 952 | "uid": "${DS_PROMETHEUS}" 953 | }, 954 | "editorMode": "code", 955 | "expr": "ilo_power_current_watt{host='$node'}", 956 | "interval": "", 957 | "legendFormat": "Current", 958 | "range": true, 959 | "refId": "A" 960 | }, 961 | { 962 | "datasource": { 963 | "type": "prometheus", 964 | "uid": "${DS_PROMETHEUS}" 965 | }, 966 | "editorMode": "code", 967 | "expr": "ilo_power_average_watt{host='$node'}", 968 | "hide": false, 969 | "interval": "", 970 | "legendFormat": "Average", 971 | "range": true, 972 | "refId": "B" 973 | }, 974 | { 975 | "datasource": { 976 | "type": "prometheus", 977 | "uid": "${DS_PROMETHEUS}" 978 | }, 979 | "editorMode": "code", 980 | "expr": "ilo_power_min_watt{host='$node'}", 981 | "hide": false, 982 | "interval": "", 983 | "legendFormat": "Min", 984 | "range": true, 985 | "refId": "C" 986 | }, 987 | { 988 | "datasource": { 989 | "type": "prometheus", 990 | "uid": "${DS_PROMETHEUS}" 991 | }, 992 | "editorMode": "code", 993 | "expr": "ilo_power_max_watt{host='$node'}", 994 | "hide": false, 995 | "interval": "", 996 | "legendFormat": "Max", 997 | "range": true, 998 | "refId": "D" 999 | } 1000 | ], 1001 | "title": "Power (W)", 1002 | "type": "timeseries" 1003 | }, 1004 | { 1005 | "datasource": { 1006 | "type": "prometheus", 1007 | "uid": "${DS_PROMETHEUS}" 1008 | }, 1009 | "gridPos": { 1010 | "h": 8, 1011 | "w": 24, 1012 | "x": 0, 1013 | "y": 17 1014 | }, 1015 | "id": 20, 1016 | "options": { 1017 | "alertInstanceLabelFilter": "{dashboard=\"iLO\"}", 1018 | "alertName": "", 1019 | "dashboardAlerts": false, 1020 | "groupBy": [], 1021 | "groupMode": "default", 1022 | "maxItems": 20, 1023 | "sortOrder": 1, 1024 | "stateFilter": { 1025 | "error": true, 1026 | "firing": true, 1027 | "noData": true, 1028 | "normal": false, 1029 | "pending": true 1030 | }, 1031 | "viewMode": "list" 1032 | }, 1033 | "title": "Alerts", 1034 | "type": "alertlist" 1035 | } 1036 | ], 1037 | "refresh": "", 1038 | "schemaVersion": 38, 1039 | "style": "dark", 1040 | "tags": [ 1041 | "ilo", 1042 | "ipmi" 1043 | ], 1044 | "templating": { 1045 | "list": [ 1046 | { 1047 | "current": {}, 1048 | "datasource": { 1049 | "type": "prometheus", 1050 | "uid": "${DS_PROMETHEUS}" 1051 | }, 1052 | "definition": "label_values(ilo_manager_info,host)", 1053 | "hide": 0, 1054 | "includeAll": false, 1055 | "label": "HOST:", 1056 | "multi": false, 1057 | "name": "node", 1058 | "options": [], 1059 | "query": { 1060 | "query": "label_values(ilo_manager_info,host)", 1061 | "refId": "PrometheusVariableQueryEditor-VariableQuery" 1062 | }, 1063 | "refresh": 1, 1064 | "regex": "", 1065 | "skipUrlSync": false, 1066 | "sort": 1, 1067 | "tagValuesQuery": "", 1068 | "tagsQuery": "", 1069 | "type": "query", 1070 | "useTags": false 1071 | } 1072 | ] 1073 | }, 1074 | "time": { 1075 | "from": "now-6h", 1076 | "to": "now" 1077 | }, 1078 | "timepicker": {}, 1079 | "timezone": "", 1080 | "title": "iLO", 1081 | "uid": "tUMY2B5Mk", 1082 | "version": 2, 1083 | "weekStart": "" 1084 | } -------------------------------------------------------------------------------- /ilo-grafana-alerts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | groups: 3 | - orgId: 1 4 | name: ilo-tf 5 | folder: Hosts 6 | interval: 1m 7 | rules: 8 | - uid: fb4fc6b9-2005-4995-b011-71f90bfc25c4 9 | title: Chassis Fan Enabled 10 | condition: C 11 | data: 12 | - refId: A 13 | relativeTimeRange: 14 | from: 600 15 | to: 0 16 | datasourceUid: k2I1C4hVz 17 | model: 18 | datasource: 19 | type: prometheus 20 | uid: k2I1C4hVz 21 | editorMode: code 22 | exemplar: false 23 | expr: ilo_chassis_fan_enabled[10m] 24 | instant: true 25 | interval: "" 26 | intervalMs: 5000 27 | legendFormat: __auto 28 | maxDataPoints: 43200 29 | range: false 30 | refId: A 31 | - refId: B 32 | relativeTimeRange: 33 | from: 600 34 | to: 0 35 | datasourceUid: "-100" 36 | model: 37 | conditions: 38 | - evaluator: 39 | params: [] 40 | type: gt 41 | operator: 42 | type: and 43 | query: 44 | params: 45 | - B 46 | reducer: 47 | params: [] 48 | type: last 49 | type: query 50 | datasource: 51 | type: __expr__ 52 | uid: "-100" 53 | expression: A 54 | hide: false 55 | intervalMs: 1000 56 | maxDataPoints: 43200 57 | reducer: min 58 | refId: B 59 | type: reduce 60 | - refId: C 61 | relativeTimeRange: 62 | from: 600 63 | to: 0 64 | datasourceUid: "-100" 65 | model: 66 | conditions: 67 | - evaluator: 68 | params: 69 | - 1 70 | type: lt 71 | operator: 72 | type: and 73 | query: 74 | params: 75 | - C 76 | reducer: 77 | params: [] 78 | type: last 79 | type: query 80 | datasource: 81 | type: __expr__ 82 | uid: "-100" 83 | expression: B 84 | hide: false 85 | intervalMs: 1000 86 | maxDataPoints: 43200 87 | refId: C 88 | type: threshold 89 | dashboardUid: tUMY2B5Mk 90 | panelId: 3 91 | noDataState: NoData 92 | execErrState: Error 93 | for: 10m 94 | annotations: 95 | __dashboardUid__: tUMY2B5Mk 96 | __panelId__: "3" 97 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} enabled was {{ printf "%.2f" $values.B.Value }} in the last minute' 98 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} is disabled' 99 | labels: 100 | Severity: warning 101 | dashboard: iLO 102 | isPaused: false 103 | - uid: fc3256d0-1e96-4851-957a-fc7ab0c30f2b 104 | title: Chassis Fan Healthy 105 | condition: C 106 | data: 107 | - refId: A 108 | relativeTimeRange: 109 | from: 600 110 | to: 0 111 | datasourceUid: k2I1C4hVz 112 | model: 113 | datasource: 114 | type: prometheus 115 | uid: k2I1C4hVz 116 | editorMode: code 117 | exemplar: false 118 | expr: ilo_chassis_fan_healthy[10m] 119 | instant: true 120 | interval: "" 121 | intervalMs: 5000 122 | legendFormat: __auto 123 | maxDataPoints: 43200 124 | range: false 125 | refId: A 126 | - refId: B 127 | relativeTimeRange: 128 | from: 600 129 | to: 0 130 | datasourceUid: "-100" 131 | model: 132 | conditions: 133 | - evaluator: 134 | params: [] 135 | type: gt 136 | operator: 137 | type: and 138 | query: 139 | params: 140 | - B 141 | reducer: 142 | params: [] 143 | type: last 144 | type: query 145 | datasource: 146 | type: __expr__ 147 | uid: "-100" 148 | expression: A 149 | hide: false 150 | intervalMs: 1000 151 | maxDataPoints: 43200 152 | reducer: min 153 | refId: B 154 | type: reduce 155 | - refId: C 156 | relativeTimeRange: 157 | from: 600 158 | to: 0 159 | datasourceUid: "-100" 160 | model: 161 | conditions: 162 | - evaluator: 163 | params: 164 | - 1 165 | type: lt 166 | operator: 167 | type: and 168 | query: 169 | params: 170 | - C 171 | reducer: 172 | params: [] 173 | type: last 174 | type: query 175 | datasource: 176 | type: __expr__ 177 | uid: "-100" 178 | expression: B 179 | hide: false 180 | intervalMs: 1000 181 | maxDataPoints: 43200 182 | refId: C 183 | type: threshold 184 | dashboardUid: tUMY2B5Mk 185 | panelId: 3 186 | noDataState: NoData 187 | execErrState: Error 188 | for: 10m 189 | annotations: 190 | __dashboardUid__: tUMY2B5Mk 191 | __panelId__: "3" 192 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} healthy was {{ printf "%.2f" $values.B.Value }} in the last minute' 193 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} is unhealthy' 194 | labels: 195 | Severity: warning 196 | dashboard: iLO 197 | isPaused: false 198 | - uid: f2a6672f-e9f6-43de-a6d0-520d11c16af3 199 | title: Chassis Temperature Healthy 200 | condition: C 201 | data: 202 | - refId: A 203 | relativeTimeRange: 204 | from: 600 205 | to: 0 206 | datasourceUid: k2I1C4hVz 207 | model: 208 | datasource: 209 | type: prometheus 210 | uid: k2I1C4hVz 211 | editorMode: code 212 | exemplar: false 213 | expr: ilo_chassis_temperature_healthy[10m] 214 | instant: true 215 | interval: "" 216 | intervalMs: 5000 217 | legendFormat: __auto 218 | maxDataPoints: 43200 219 | range: false 220 | refId: A 221 | - refId: B 222 | relativeTimeRange: 223 | from: 600 224 | to: 0 225 | datasourceUid: "-100" 226 | model: 227 | conditions: 228 | - evaluator: 229 | params: [] 230 | type: gt 231 | operator: 232 | type: and 233 | query: 234 | params: 235 | - B 236 | reducer: 237 | params: [] 238 | type: last 239 | type: query 240 | datasource: 241 | type: __expr__ 242 | uid: "-100" 243 | expression: A 244 | hide: false 245 | intervalMs: 1000 246 | maxDataPoints: 43200 247 | reducer: min 248 | refId: B 249 | type: reduce 250 | - refId: C 251 | relativeTimeRange: 252 | from: 600 253 | to: 0 254 | datasourceUid: "-100" 255 | model: 256 | conditions: 257 | - evaluator: 258 | params: 259 | - 1 260 | type: lt 261 | operator: 262 | type: and 263 | query: 264 | params: 265 | - C 266 | reducer: 267 | params: [] 268 | type: last 269 | type: query 270 | datasource: 271 | type: __expr__ 272 | uid: "-100" 273 | expression: B 274 | hide: false 275 | intervalMs: 1000 276 | maxDataPoints: 43200 277 | refId: C 278 | type: threshold 279 | dashboardUid: tUMY2B5Mk 280 | panelId: 2 281 | noDataState: NoData 282 | execErrState: Error 283 | for: 10m 284 | annotations: 285 | __dashboardUid__: tUMY2B5Mk 286 | __panelId__: "2" 287 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} healthy was {{ printf "%.2f" $values.B.Value }} in the last minute' 288 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} is unhealthy' 289 | labels: 290 | Severity: warning 291 | dashboard: iLO 292 | isPaused: false 293 | - uid: ca0af254-b35e-4c63-853a-880dd6d5d2c8 294 | title: Temperature Critical 295 | condition: C 296 | data: 297 | - refId: A 298 | relativeTimeRange: 299 | from: 600 300 | to: 0 301 | datasourceUid: k2I1C4hVz 302 | model: 303 | datasource: 304 | type: prometheus 305 | uid: k2I1C4hVz 306 | editorMode: code 307 | exemplar: true 308 | expr: ((ilo_chassis_temperature_critical > 0) and (max_over_time(ilo_chassis_temperature_critical[10m]) - max_over_time(ilo_chassis_temperature_current[10m]))) * -1 309 | interval: "" 310 | intervalMs: 5000 311 | legendFormat: __auto 312 | maxDataPoints: 43200 313 | range: true 314 | refId: A 315 | - refId: B 316 | relativeTimeRange: 317 | from: 600 318 | to: 0 319 | datasourceUid: "-100" 320 | model: 321 | conditions: 322 | - evaluator: 323 | params: [] 324 | type: gt 325 | operator: 326 | type: and 327 | query: 328 | params: 329 | - B 330 | reducer: 331 | params: [] 332 | type: last 333 | type: query 334 | datasource: 335 | type: __expr__ 336 | uid: "-100" 337 | expression: A 338 | hide: false 339 | intervalMs: 1000 340 | maxDataPoints: 43200 341 | reducer: max 342 | refId: B 343 | type: reduce 344 | - refId: C 345 | relativeTimeRange: 346 | from: 600 347 | to: 0 348 | datasourceUid: "-100" 349 | model: 350 | conditions: 351 | - evaluator: 352 | params: 353 | - 0.1 354 | type: gt 355 | operator: 356 | type: and 357 | query: 358 | params: 359 | - C 360 | reducer: 361 | params: [] 362 | type: last 363 | type: query 364 | datasource: 365 | type: __expr__ 366 | uid: "-100" 367 | expression: B 368 | hide: false 369 | intervalMs: 1000 370 | maxDataPoints: 43200 371 | refId: C 372 | type: threshold 373 | dashboardUid: tUMY2B5Mk 374 | panelId: 2 375 | noDataState: NoData 376 | execErrState: Error 377 | for: 10m 378 | annotations: 379 | __dashboardUid__: tUMY2B5Mk 380 | __panelId__: "2" 381 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} temperature is Critical' 382 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} temperature is {{ printf "%.2f" $values.B.Value }}°C over the Critical threshold!' 383 | labels: 384 | Severity: critical 385 | dashboard: iLO 386 | isPaused: false 387 | - uid: b1458be7-7483-4cc5-8ec7-1c840feec39d 388 | title: Temperature Fatal 389 | condition: C 390 | data: 391 | - refId: A 392 | relativeTimeRange: 393 | from: 600 394 | to: 0 395 | datasourceUid: k2I1C4hVz 396 | model: 397 | datasource: 398 | type: prometheus 399 | uid: k2I1C4hVz 400 | editorMode: code 401 | exemplar: true 402 | expr: ((ilo_chassis_temperature_fatal > 0) and (max_over_time(ilo_chassis_temperature_fatal[10m]) - max_over_time(ilo_chassis_temperature_current[10m]))) * -1 403 | interval: "" 404 | intervalMs: 5000 405 | legendFormat: __auto 406 | maxDataPoints: 43200 407 | range: true 408 | refId: A 409 | - refId: B 410 | relativeTimeRange: 411 | from: 600 412 | to: 0 413 | datasourceUid: "-100" 414 | model: 415 | conditions: 416 | - evaluator: 417 | params: [] 418 | type: gt 419 | operator: 420 | type: and 421 | query: 422 | params: 423 | - B 424 | reducer: 425 | params: [] 426 | type: last 427 | type: query 428 | datasource: 429 | type: __expr__ 430 | uid: "-100" 431 | expression: A 432 | hide: false 433 | intervalMs: 1000 434 | maxDataPoints: 43200 435 | reducer: max 436 | refId: B 437 | type: reduce 438 | - refId: C 439 | relativeTimeRange: 440 | from: 600 441 | to: 0 442 | datasourceUid: "-100" 443 | model: 444 | conditions: 445 | - evaluator: 446 | params: 447 | - 0.1 448 | type: gt 449 | operator: 450 | type: and 451 | query: 452 | params: 453 | - C 454 | reducer: 455 | params: [] 456 | type: last 457 | type: query 458 | datasource: 459 | type: __expr__ 460 | uid: "-100" 461 | expression: B 462 | hide: false 463 | intervalMs: 1000 464 | maxDataPoints: 43200 465 | refId: C 466 | type: threshold 467 | dashboardUid: tUMY2B5Mk 468 | panelId: 2 469 | noDataState: NoData 470 | execErrState: Error 471 | for: 10m 472 | annotations: 473 | __dashboardUid__: tUMY2B5Mk 474 | __panelId__: "2" 475 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} temperature is FATAL' 476 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} temperature is {{ printf "%.2f" $values.B.Value }}°C over the FATAL threshold!' 477 | labels: 478 | Severity: critical 479 | dashboard: iLO 480 | isPaused: false 481 | - uid: b1bf599f-66f8-44e9-9290-49ebf82b20c2 482 | title: DIMM Healthy 483 | condition: C 484 | data: 485 | - refId: A 486 | relativeTimeRange: 487 | from: 600 488 | to: 0 489 | datasourceUid: k2I1C4hVz 490 | model: 491 | datasource: 492 | type: prometheus 493 | uid: k2I1C4hVz 494 | editorMode: code 495 | exemplar: false 496 | expr: ilo_memory_dimm_healthy[10m] 497 | instant: true 498 | interval: "" 499 | intervalMs: 5000 500 | legendFormat: __auto 501 | maxDataPoints: 43200 502 | range: false 503 | refId: A 504 | - refId: B 505 | relativeTimeRange: 506 | from: 600 507 | to: 0 508 | datasourceUid: "-100" 509 | model: 510 | conditions: 511 | - evaluator: 512 | params: [] 513 | type: gt 514 | operator: 515 | type: and 516 | query: 517 | params: 518 | - B 519 | reducer: 520 | params: [] 521 | type: last 522 | type: query 523 | datasource: 524 | type: __expr__ 525 | uid: "-100" 526 | expression: A 527 | hide: false 528 | intervalMs: 1000 529 | maxDataPoints: 43200 530 | reducer: min 531 | refId: B 532 | type: reduce 533 | - refId: C 534 | relativeTimeRange: 535 | from: 600 536 | to: 0 537 | datasourceUid: "-100" 538 | model: 539 | conditions: 540 | - evaluator: 541 | params: 542 | - 1 543 | type: lt 544 | operator: 545 | type: and 546 | query: 547 | params: 548 | - C 549 | reducer: 550 | params: [] 551 | type: last 552 | type: query 553 | datasource: 554 | type: __expr__ 555 | uid: "-100" 556 | expression: B 557 | hide: false 558 | intervalMs: 1000 559 | maxDataPoints: 43200 560 | refId: C 561 | type: threshold 562 | dashboardUid: tUMY2B5Mk 563 | panelId: 15 564 | noDataState: NoData 565 | execErrState: Error 566 | for: 10m 567 | annotations: 568 | __dashboardUid__: tUMY2B5Mk 569 | __panelId__: "15" 570 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} healthy was {{ printf "%.2f" $values.B.Value }} in the last minute' 571 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.name }} is unhealthy' 572 | labels: 573 | Severity: warning 574 | dashboard: iLO 575 | isPaused: false 576 | - uid: a778084f-eb8a-460f-a651-8e0c63777bf9 577 | title: High Max Power Consumption 578 | condition: C 579 | data: 580 | - refId: A 581 | relativeTimeRange: 582 | from: 300 583 | to: 0 584 | datasourceUid: k2I1C4hVz 585 | model: 586 | datasource: 587 | type: prometheus 588 | uid: k2I1C4hVz 589 | editorMode: code 590 | exemplar: true 591 | expr: max_over_time(ilo_power_max_watt[10m]) / max_over_time(ilo_power_capacity_watt[10m]) * 100 592 | interval: "" 593 | intervalMs: 5000 594 | legendFormat: __auto 595 | maxDataPoints: 43200 596 | range: true 597 | refId: A 598 | - refId: B 599 | relativeTimeRange: 600 | from: 300 601 | to: 0 602 | datasourceUid: "-100" 603 | model: 604 | conditions: 605 | - evaluator: 606 | params: [] 607 | type: gt 608 | operator: 609 | type: and 610 | query: 611 | params: 612 | - B 613 | reducer: 614 | params: [] 615 | type: last 616 | type: query 617 | datasource: 618 | type: __expr__ 619 | uid: "-100" 620 | expression: A 621 | hide: false 622 | intervalMs: 1000 623 | maxDataPoints: 43200 624 | reducer: max 625 | refId: B 626 | type: reduce 627 | - refId: C 628 | relativeTimeRange: 629 | from: 300 630 | to: 0 631 | datasourceUid: "-100" 632 | model: 633 | conditions: 634 | - evaluator: 635 | params: 636 | - 50 637 | type: gt 638 | operator: 639 | type: and 640 | query: 641 | params: 642 | - C 643 | reducer: 644 | params: [] 645 | type: last 646 | type: query 647 | datasource: 648 | type: __expr__ 649 | uid: "-100" 650 | expression: B 651 | hide: false 652 | intervalMs: 1000 653 | maxDataPoints: 43200 654 | refId: C 655 | type: threshold 656 | dashboardUid: tUMY2B5Mk 657 | panelId: 4 658 | noDataState: NoData 659 | execErrState: Error 660 | for: 10m 661 | annotations: 662 | __dashboardUid__: tUMY2B5Mk 663 | __panelId__: "4" 664 | description: '{{ $values.B.Labels.host }} max power consumption over the last minute was {{ printf "%.2f" $values.B.Value }}% of capacity' 665 | summary: '{{ $values.B.Labels.host }} high power consumption' 666 | labels: 667 | Severity: warning 668 | dashboard: iLO 669 | isPaused: false 670 | - uid: dd05c127-41ae-4134-95f7-882a3aaab5bb 671 | title: Power Supply Enabled 672 | condition: C 673 | data: 674 | - refId: A 675 | relativeTimeRange: 676 | from: 600 677 | to: 0 678 | datasourceUid: k2I1C4hVz 679 | model: 680 | datasource: 681 | type: prometheus 682 | uid: k2I1C4hVz 683 | editorMode: code 684 | exemplar: false 685 | expr: ilo_power_supply_enabled[10m] 686 | instant: true 687 | interval: "" 688 | intervalMs: 5000 689 | legendFormat: __auto 690 | maxDataPoints: 43200 691 | range: false 692 | refId: A 693 | - refId: B 694 | relativeTimeRange: 695 | from: 600 696 | to: 0 697 | datasourceUid: "-100" 698 | model: 699 | conditions: 700 | - evaluator: 701 | params: [] 702 | type: gt 703 | operator: 704 | type: and 705 | query: 706 | params: 707 | - B 708 | reducer: 709 | params: [] 710 | type: last 711 | type: query 712 | datasource: 713 | type: __expr__ 714 | uid: "-100" 715 | expression: A 716 | hide: false 717 | intervalMs: 1000 718 | maxDataPoints: 43200 719 | reducer: min 720 | refId: B 721 | type: reduce 722 | - refId: C 723 | relativeTimeRange: 724 | from: 600 725 | to: 0 726 | datasourceUid: "-100" 727 | model: 728 | conditions: 729 | - evaluator: 730 | params: 731 | - 1 732 | type: lt 733 | operator: 734 | type: and 735 | query: 736 | params: 737 | - C 738 | reducer: 739 | params: [] 740 | type: last 741 | type: query 742 | datasource: 743 | type: __expr__ 744 | uid: "-100" 745 | expression: B 746 | hide: false 747 | intervalMs: 1000 748 | maxDataPoints: 43200 749 | refId: C 750 | type: threshold 751 | dashboardUid: tUMY2B5Mk 752 | panelId: 16 753 | noDataState: NoData 754 | execErrState: Error 755 | for: 10m 756 | annotations: 757 | __dashboardUid__: tUMY2B5Mk 758 | __panelId__: "16" 759 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.serial }} power supply was not enabled in the last minute' 760 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.serial }} power supply disabled' 761 | labels: 762 | Severity: warning 763 | dashboard: iLO 764 | isPaused: false 765 | - uid: b80043e8-992b-46dd-908a-a17f29b1d740 766 | title: Power Supply Unhealthy 767 | condition: C 768 | data: 769 | - refId: A 770 | relativeTimeRange: 771 | from: 600 772 | to: 0 773 | datasourceUid: k2I1C4hVz 774 | model: 775 | datasource: 776 | type: prometheus 777 | uid: k2I1C4hVz 778 | editorMode: code 779 | exemplar: false 780 | expr: ilo_power_supply_healthy[10m] 781 | instant: true 782 | interval: "" 783 | intervalMs: 5000 784 | legendFormat: __auto 785 | maxDataPoints: 43200 786 | range: false 787 | refId: A 788 | - refId: B 789 | relativeTimeRange: 790 | from: 600 791 | to: 0 792 | datasourceUid: "-100" 793 | model: 794 | conditions: 795 | - evaluator: 796 | params: [] 797 | type: gt 798 | operator: 799 | type: and 800 | query: 801 | params: 802 | - B 803 | reducer: 804 | params: [] 805 | type: last 806 | type: query 807 | datasource: 808 | type: __expr__ 809 | uid: "-100" 810 | expression: A 811 | hide: false 812 | intervalMs: 1000 813 | maxDataPoints: 43200 814 | reducer: min 815 | refId: B 816 | type: reduce 817 | - refId: C 818 | relativeTimeRange: 819 | from: 600 820 | to: 0 821 | datasourceUid: "-100" 822 | model: 823 | conditions: 824 | - evaluator: 825 | params: 826 | - 1 827 | type: lt 828 | operator: 829 | type: and 830 | query: 831 | params: 832 | - C 833 | reducer: 834 | params: [] 835 | type: last 836 | type: query 837 | datasource: 838 | type: __expr__ 839 | uid: "-100" 840 | expression: B 841 | hide: false 842 | intervalMs: 1000 843 | maxDataPoints: 43200 844 | refId: C 845 | type: threshold 846 | dashboardUid: tUMY2B5Mk 847 | panelId: 17 848 | noDataState: NoData 849 | execErrState: Error 850 | for: 10m 851 | annotations: 852 | __dashboardUid__: tUMY2B5Mk 853 | __panelId__: "17" 854 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.serial }} power supply was not healthy in the last minute' 855 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.serial }} power supply unhealthy' 856 | labels: 857 | Severity: warning 858 | dashboard: iLO 859 | isPaused: false 860 | - uid: ffd32292-9a1f-43df-aa14-c00bfcfd283d 861 | title: Power Up 862 | condition: C 863 | data: 864 | - refId: A 865 | relativeTimeRange: 866 | from: 600 867 | to: 0 868 | datasourceUid: k2I1C4hVz 869 | model: 870 | datasource: 871 | type: prometheus 872 | uid: k2I1C4hVz 873 | editorMode: code 874 | exemplar: false 875 | expr: ilo_power_up[10m] 876 | instant: true 877 | interval: "" 878 | intervalMs: 5000 879 | legendFormat: __auto 880 | maxDataPoints: 43200 881 | range: false 882 | refId: A 883 | - refId: B 884 | relativeTimeRange: 885 | from: 600 886 | to: 0 887 | datasourceUid: "-100" 888 | model: 889 | conditions: 890 | - evaluator: 891 | params: [] 892 | type: gt 893 | operator: 894 | type: and 895 | query: 896 | params: 897 | - B 898 | reducer: 899 | params: [] 900 | type: last 901 | type: query 902 | datasource: 903 | type: __expr__ 904 | uid: "-100" 905 | expression: A 906 | hide: false 907 | intervalMs: 1000 908 | maxDataPoints: 43200 909 | reducer: min 910 | refId: B 911 | type: reduce 912 | - refId: C 913 | relativeTimeRange: 914 | from: 600 915 | to: 0 916 | datasourceUid: "-100" 917 | model: 918 | conditions: 919 | - evaluator: 920 | params: 921 | - 1 922 | type: lt 923 | operator: 924 | type: and 925 | query: 926 | params: 927 | - C 928 | reducer: 929 | params: [] 930 | type: last 931 | type: query 932 | datasource: 933 | type: __expr__ 934 | uid: "-100" 935 | expression: B 936 | hide: false 937 | intervalMs: 1000 938 | maxDataPoints: 43200 939 | refId: C 940 | type: threshold 941 | dashboardUid: tUMY2B5Mk 942 | panelId: 8 943 | noDataState: NoData 944 | execErrState: Error 945 | for: 10m 946 | annotations: 947 | __dashboardUid__: tUMY2B5Mk 948 | __panelId__: "8" 949 | description: '{{ $values.B.Labels.host }} iLO Power Up was {{ printf "%.2f" $values.B.Value }} in the last minute' 950 | summary: '{{ $values.B.Labels.host }} iLO is powered off' 951 | labels: 952 | Severity: warning 953 | dashboard: iLO 954 | isPaused: false 955 | - uid: fae1f39d-4816-4365-a5ec-a93dcaa702d2 956 | title: Processor Unhealthy 957 | condition: C 958 | data: 959 | - refId: A 960 | relativeTimeRange: 961 | from: 600 962 | to: 0 963 | datasourceUid: k2I1C4hVz 964 | model: 965 | datasource: 966 | type: prometheus 967 | uid: k2I1C4hVz 968 | editorMode: code 969 | exemplar: false 970 | expr: ilo_processor_healthy[10m] 971 | instant: true 972 | interval: "" 973 | intervalMs: 5000 974 | legendFormat: __auto 975 | maxDataPoints: 43200 976 | range: false 977 | refId: A 978 | - refId: B 979 | relativeTimeRange: 980 | from: 600 981 | to: 0 982 | datasourceUid: "-100" 983 | model: 984 | conditions: 985 | - evaluator: 986 | params: [] 987 | type: gt 988 | operator: 989 | type: and 990 | query: 991 | params: 992 | - B 993 | reducer: 994 | params: [] 995 | type: last 996 | type: query 997 | datasource: 998 | type: __expr__ 999 | uid: "-100" 1000 | expression: A 1001 | hide: false 1002 | intervalMs: 1000 1003 | maxDataPoints: 43200 1004 | reducer: min 1005 | refId: B 1006 | type: reduce 1007 | - refId: C 1008 | relativeTimeRange: 1009 | from: 600 1010 | to: 0 1011 | datasourceUid: "-100" 1012 | model: 1013 | conditions: 1014 | - evaluator: 1015 | params: 1016 | - 1 1017 | type: lt 1018 | operator: 1019 | type: and 1020 | query: 1021 | params: 1022 | - C 1023 | reducer: 1024 | params: [] 1025 | type: last 1026 | type: query 1027 | datasource: 1028 | type: __expr__ 1029 | uid: "-100" 1030 | expression: B 1031 | hide: false 1032 | intervalMs: 1000 1033 | maxDataPoints: 43200 1034 | refId: C 1035 | type: threshold 1036 | dashboardUid: tUMY2B5Mk 1037 | panelId: 18 1038 | noDataState: NoData 1039 | execErrState: Error 1040 | for: 10m 1041 | annotations: 1042 | __dashboardUid__: tUMY2B5Mk 1043 | __panelId__: "18" 1044 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.socket }} power supply was not healthy in the last minute' 1045 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.socket }} processor unhealthy' 1046 | labels: 1047 | Severity: warning 1048 | dashboard: iLO 1049 | isPaused: false 1050 | - uid: ef3315c8-835e-432d-b8ee-b24a1be798e1 1051 | title: Disk Unhealthy 1052 | condition: C 1053 | data: 1054 | - refId: A 1055 | relativeTimeRange: 1056 | from: 600 1057 | to: 0 1058 | datasourceUid: k2I1C4hVz 1059 | model: 1060 | datasource: 1061 | type: prometheus 1062 | uid: k2I1C4hVz 1063 | editorMode: code 1064 | exemplar: false 1065 | expr: ilo_storage_disk_healthy[10m] 1066 | instant: true 1067 | interval: "" 1068 | intervalMs: 5000 1069 | legendFormat: __auto 1070 | maxDataPoints: 43200 1071 | range: false 1072 | refId: A 1073 | - refId: B 1074 | relativeTimeRange: 1075 | from: 600 1076 | to: 0 1077 | datasourceUid: "-100" 1078 | model: 1079 | conditions: 1080 | - evaluator: 1081 | params: [] 1082 | type: gt 1083 | operator: 1084 | type: and 1085 | query: 1086 | params: 1087 | - B 1088 | reducer: 1089 | params: [] 1090 | type: last 1091 | type: query 1092 | datasource: 1093 | type: __expr__ 1094 | uid: "-100" 1095 | expression: A 1096 | hide: false 1097 | intervalMs: 1000 1098 | maxDataPoints: 43200 1099 | reducer: min 1100 | refId: B 1101 | type: reduce 1102 | - refId: C 1103 | relativeTimeRange: 1104 | from: 600 1105 | to: 0 1106 | datasourceUid: "-100" 1107 | model: 1108 | conditions: 1109 | - evaluator: 1110 | params: 1111 | - 1 1112 | type: lt 1113 | operator: 1114 | type: and 1115 | query: 1116 | params: 1117 | - C 1118 | reducer: 1119 | params: [] 1120 | type: last 1121 | type: query 1122 | datasource: 1123 | type: __expr__ 1124 | uid: "-100" 1125 | expression: B 1126 | hide: false 1127 | intervalMs: 1000 1128 | maxDataPoints: 43200 1129 | refId: C 1130 | type: threshold 1131 | dashboardUid: tUMY2B5Mk 1132 | panelId: 19 1133 | noDataState: NoData 1134 | execErrState: Error 1135 | for: 10m 1136 | annotations: 1137 | __dashboardUid__: tUMY2B5Mk 1138 | __panelId__: "19" 1139 | description: '{{ $values.B.Labels.host }} {{ $values.B.Labels.location }} disk was not healthy in the last minute' 1140 | summary: '{{ $values.B.Labels.host }} {{ $values.B.Labels.location }} disk unhealthy' 1141 | labels: 1142 | Severity: warning 1143 | dashboard: iLO 1144 | isPaused: false 1145 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "flag" 10 | "fmt" 11 | "log" 12 | "net/http" 13 | "os" 14 | "os/signal" 15 | 16 | "github.com/MauveSoftware/ilo_exporter/pkg/chassis" 17 | "github.com/MauveSoftware/ilo_exporter/pkg/client" 18 | "github.com/MauveSoftware/ilo_exporter/pkg/manager" 19 | "github.com/MauveSoftware/ilo_exporter/pkg/system" 20 | "go.opentelemetry.io/otel/attribute" 21 | "go.opentelemetry.io/otel/trace" 22 | 23 | "github.com/prometheus/client_golang/prometheus" 24 | "github.com/prometheus/client_golang/prometheus/promhttp" 25 | "github.com/sirupsen/logrus" 26 | ) 27 | 28 | const version string = "1.0.2" 29 | 30 | var ( 31 | showVersion = flag.Bool("version", false, "Print version information.") 32 | listenAddress = flag.String("web.listen-address", ":9545", "Address on which to expose metrics and web interface.") 33 | metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") 34 | username = flag.String("api.username", "", "Username") 35 | password = flag.String("api.password", "", "Password") 36 | maxConcurrentRequests = flag.Uint("api.max-concurrent-requests", 4, "Maximum number of requests sent against API concurrently") 37 | tlsEnabled = flag.Bool("tls.enabled", false, "Enables TLS") 38 | tlsCertChainPath = flag.String("tls.cert-file", "", "Path to TLS cert file") 39 | tlsKeyPath = flag.String("tls.key-file", "", "Path to TLS key file") 40 | tracingEnabled = flag.Bool("tracing.enabled", false, "Enables tracing using OpenTelemetry") 41 | tracingProvider = flag.String("tracing.provider", "", "Sets the tracing provider (stdout or collector)") 42 | tracingCollectorEndpoint = flag.String("tracing.collector.grpc-endpoint", "", "Sets the tracing provider (stdout or collector)") 43 | ) 44 | 45 | func init() { 46 | flag.Usage = func() { 47 | fmt.Println("Usage: ilo_exporter [ ... ]\n\nParameters:") 48 | fmt.Println() 49 | flag.PrintDefaults() 50 | } 51 | } 52 | 53 | func main() { 54 | flag.Parse() 55 | 56 | if *showVersion { 57 | printVersion() 58 | os.Exit(0) 59 | } 60 | 61 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) 62 | defer cancel() 63 | 64 | shutdownTracing, err := initTracing(ctx) 65 | if err != nil { 66 | log.Fatalf("could not initialize tracing: %v", err) 67 | } 68 | defer shutdownTracing() 69 | 70 | startServer() 71 | } 72 | 73 | func printVersion() { 74 | fmt.Println("ilo_exporter") 75 | fmt.Printf("Version: %s\n", version) 76 | fmt.Println("Author(s): Daniel Czerwonk") 77 | fmt.Println("Copyright: 2022, Mauve Mailorder Software GmbH & Co. KG, Licensed under MIT license") 78 | fmt.Println("Metric exporter for HP iLO") 79 | } 80 | 81 | func startServer() { 82 | logrus.Infof("Starting iLO exporter (Version: %s)", version) 83 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 84 | w.Write([]byte(` 85 | iLO5 Exporter (Version ` + version + `) 86 | 87 |

iLO Exporter by Mauve Mailorder Software

88 |

Example

89 |

Metrics for host 172.16.0.200

90 |

` + r.Host + *metricsPath + `?host=172.16.0.200

91 |

More information

92 |

github.com/MauveSoftware/ilo_exporter

93 | 94 | `)) 95 | }) 96 | http.HandleFunc(*metricsPath, errorHandler(handleMetricsRequest)) 97 | 98 | logrus.Infof("Listening for %s on %s (TLS: %v)", *metricsPath, *listenAddress, *tlsEnabled) 99 | if *tlsEnabled { 100 | logrus.Fatal(http.ListenAndServeTLS(*listenAddress, *tlsCertChainPath, *tlsKeyPath, nil)) 101 | return 102 | } 103 | 104 | logrus.Fatal(http.ListenAndServe(*listenAddress, nil)) 105 | } 106 | 107 | func errorHandler(f func(http.ResponseWriter, *http.Request) error) http.HandlerFunc { 108 | return func(w http.ResponseWriter, r *http.Request) { 109 | err := f(w, r) 110 | 111 | if err != nil { 112 | logrus.Errorln(err) 113 | http.Error(w, err.Error(), http.StatusInternalServerError) 114 | } 115 | } 116 | } 117 | 118 | func handleMetricsRequest(w http.ResponseWriter, r *http.Request) error { 119 | host := r.URL.Query().Get("host") 120 | 121 | ctx, span := tracer.Start(r.Context(), "HandleMetricsRequest", trace.WithAttributes( 122 | attribute.String("host", host), 123 | )) 124 | defer span.End() 125 | 126 | if host == "" { 127 | return fmt.Errorf("no host defined") 128 | } 129 | 130 | reg := prometheus.NewRegistry() 131 | 132 | cl := client.NewClient(host, *username, *password, tracer, client.WithMaxConcurrentRequests(*maxConcurrentRequests), client.WithInsecure(), client.WithDebug()) 133 | reg.MustRegister(system.NewCollector(ctx, cl, tracer)) 134 | reg.MustRegister(manager.NewCollector(ctx, cl, tracer)) 135 | reg.MustRegister(chassis.NewCollector(ctx, cl, tracer)) 136 | 137 | l := logrus.New() 138 | l.Level = logrus.ErrorLevel 139 | 140 | promhttp.HandlerFor(reg, promhttp.HandlerOpts{ 141 | ErrorLog: l, 142 | ErrorHandling: promhttp.ContinueOnError}).ServeHTTP(w, r) 143 | return nil 144 | } 145 | -------------------------------------------------------------------------------- /pkg/chassis/collector.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package chassis 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "github.com/MauveSoftware/ilo_exporter/pkg/chassis/power" 12 | "github.com/MauveSoftware/ilo_exporter/pkg/chassis/thermal" 13 | "github.com/MauveSoftware/ilo_exporter/pkg/client" 14 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 15 | "github.com/prometheus/client_golang/prometheus" 16 | "go.opentelemetry.io/otel/trace" 17 | ) 18 | 19 | const ( 20 | prefix = "ilo_" 21 | ) 22 | 23 | var ( 24 | scrapeDurationDesc = prometheus.NewDesc(prefix+"chassis_scrape_duration_second", "Scrape duration for the chassis module", []string{"host"}, nil) 25 | ) 26 | 27 | // NewCollector returns a new collector for chassis metrics 28 | func NewCollector(ctx context.Context, cl client.Client, tracer trace.Tracer) prometheus.Collector { 29 | return &collector{ 30 | rootCtx: ctx, 31 | cl: cl, 32 | tracer: tracer, 33 | } 34 | } 35 | 36 | type collector struct { 37 | rootCtx context.Context 38 | cl client.Client 39 | tracer trace.Tracer 40 | } 41 | 42 | // Describe implements prometheus.Collector interface 43 | func (c *collector) Describe(ch chan<- *prometheus.Desc) { 44 | ch <- scrapeDurationDesc 45 | power.Describe(ch) 46 | thermal.Describe(ch) 47 | } 48 | 49 | // Collect implements prometheus.Collector interface 50 | func (c *collector) Collect(ch chan<- prometheus.Metric) { 51 | start := time.Now() 52 | 53 | ctx, span := c.tracer.Start(c.rootCtx, "Chassis.Collect") 54 | defer span.End() 55 | 56 | p := "Chassis/1" 57 | 58 | cc := common.NewCollectorContext(ctx, c.cl, ch, c.tracer) 59 | power.Collect(ctx, p, cc) 60 | thermal.Collect(ctx, p, cc) 61 | 62 | duration := time.Since(start).Seconds() 63 | ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, duration, c.cl.HostName()) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/chassis/power/metrics.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package power 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 12 | "github.com/prometheus/client_golang/prometheus" 13 | "go.opentelemetry.io/otel/attribute" 14 | "go.opentelemetry.io/otel/trace" 15 | ) 16 | 17 | const ( 18 | prefix = "ilo_power_" 19 | ) 20 | 21 | var ( 22 | powerCurrentDesc *prometheus.Desc 23 | powerAvgDesc *prometheus.Desc 24 | powerMinDesc *prometheus.Desc 25 | powerMaxDesc *prometheus.Desc 26 | powerSupplyHealthyDesc *prometheus.Desc 27 | powerSupplyEnabledDesc *prometheus.Desc 28 | powerCapacityDesc *prometheus.Desc 29 | ) 30 | 31 | func init() { 32 | l := []string{"host"} 33 | lpm := append(l, "id") 34 | powerCurrentDesc = prometheus.NewDesc(prefix+"current_watt", "Current power consumption in watt", lpm, nil) 35 | powerAvgDesc = prometheus.NewDesc(prefix+"average_watt", "Average power consumption in watt", lpm, nil) 36 | powerMinDesc = prometheus.NewDesc(prefix+"min_watt", "Minimum power consumption in watt", lpm, nil) 37 | powerMaxDesc = prometheus.NewDesc(prefix+"max_watt", "Maximum power consumption in watt", lpm, nil) 38 | powerCapacityDesc = prometheus.NewDesc(prefix+"capacity_watt", "Power capacity in watt", lpm, nil) 39 | 40 | l = append(l, "serial") 41 | powerSupplyHealthyDesc = prometheus.NewDesc(prefix+"supply_healthy", "Health status of the power supply", l, nil) 42 | powerSupplyEnabledDesc = prometheus.NewDesc(prefix+"supply_enabled", "Status of the power supply", l, nil) 43 | } 44 | 45 | func Describe(ch chan<- *prometheus.Desc) { 46 | ch <- powerCurrentDesc 47 | ch <- powerAvgDesc 48 | ch <- powerMinDesc 49 | ch <- powerMaxDesc 50 | ch <- powerCapacityDesc 51 | ch <- powerSupplyHealthyDesc 52 | ch <- powerSupplyEnabledDesc 53 | } 54 | 55 | func Collect(ctx context.Context, parentPath string, cc *common.CollectorContext) { 56 | ctx, span := cc.Tracer().Start(ctx, "Power.Collect", trace.WithAttributes( 57 | attribute.String("parent_path", parentPath), 58 | )) 59 | defer span.End() 60 | 61 | pwr := Power{} 62 | err := cc.Client().Get(ctx, parentPath+"/Power", &pwr) 63 | if err != nil { 64 | cc.HandleError(fmt.Errorf("could not get power data: %w", err), span) 65 | } 66 | 67 | l := []string{cc.Client().HostName()} 68 | 69 | for _, pwc := range pwr.PowerControl { 70 | la := append(l, pwc.ID) 71 | cc.RecordMetrics( 72 | prometheus.MustNewConstMetric(powerCurrentDesc, prometheus.GaugeValue, pwc.PowerConsumedWatts, la...), 73 | prometheus.MustNewConstMetric(powerAvgDesc, prometheus.GaugeValue, pwc.Metrics.AverageConsumedWatts, la...), 74 | prometheus.MustNewConstMetric(powerMinDesc, prometheus.GaugeValue, pwc.Metrics.MinConsumedWatts, la...), 75 | prometheus.MustNewConstMetric(powerMaxDesc, prometheus.GaugeValue, pwc.Metrics.MaxConsumedWatts, la...), 76 | prometheus.MustNewConstMetric(powerCapacityDesc, prometheus.GaugeValue, pwc.PowerCapacityWatts, la...), 77 | ) 78 | } 79 | 80 | for _, sup := range pwr.PowerSupplies { 81 | if sup.Status.State == "Absent" { 82 | continue 83 | } 84 | 85 | la := append(l, sup.SerialNumber) 86 | cc.RecordMetrics( 87 | prometheus.MustNewConstMetric(powerSupplyEnabledDesc, prometheus.GaugeValue, sup.Status.EnabledValue(), la...), 88 | prometheus.MustNewConstMetric(powerSupplyHealthyDesc, prometheus.GaugeValue, sup.Status.HealthValue(), la...), 89 | ) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pkg/chassis/power/power.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package power 6 | 7 | type Power struct { 8 | PowerControl []struct { 9 | ID string `json:"MemberId"` 10 | PowerCapacityWatts float64 `json:"PowerCapacityWatts"` 11 | PowerConsumedWatts float64 `json:"PowerConsumedWatts"` 12 | Metrics struct { 13 | AverageConsumedWatts float64 `json:"AverageConsumedWatts"` 14 | MaxConsumedWatts float64 `json:"MaxConsumedWatts"` 15 | MinConsumedWatts float64 `json:"MinConsumedWatts"` 16 | } `json:"PowerMetrics"` 17 | } `json:"PowerControl"` 18 | PowerSupplies []PowerSupply `json:"PowerSupplies"` 19 | } 20 | -------------------------------------------------------------------------------- /pkg/chassis/power/power_supply.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package power 6 | 7 | import ( 8 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 9 | ) 10 | 11 | type PowerSupply struct { 12 | SerialNumber string `json:"SerialNumber"` 13 | Status common.Status `json:"Status"` 14 | } 15 | -------------------------------------------------------------------------------- /pkg/chassis/thermal/fan.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package thermal 6 | 7 | import ( 8 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 9 | ) 10 | 11 | type Fan struct { 12 | NameCurrent string `json:"Name"` 13 | NameLegacy string `json:"FanName"` 14 | ReadingCurrent float64 `json:"Reading"` 15 | ReadingLegacy float64 `json:"CurrentReading"` 16 | Status common.Status `json:"Status"` 17 | } 18 | 19 | func (f *Fan) isLegacy() bool { 20 | return len(f.NameLegacy) > 0 21 | } 22 | 23 | func (f *Fan) Name() string { 24 | if f.isLegacy() { 25 | return f.NameLegacy 26 | } 27 | 28 | return f.NameCurrent 29 | } 30 | 31 | func (f *Fan) Reading() float64 { 32 | if f.isLegacy() { 33 | return f.ReadingLegacy 34 | } 35 | 36 | return f.ReadingCurrent 37 | } 38 | -------------------------------------------------------------------------------- /pkg/chassis/thermal/metrics.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package thermal 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 12 | "github.com/prometheus/client_golang/prometheus" 13 | "go.opentelemetry.io/otel/attribute" 14 | "go.opentelemetry.io/otel/trace" 15 | ) 16 | 17 | const ( 18 | prefix = "ilo_chassis_" 19 | ) 20 | 21 | var ( 22 | fanHealthyDesc *prometheus.Desc 23 | fanEnabledDesc *prometheus.Desc 24 | fanCurrentDesc *prometheus.Desc 25 | tempCurrentDesc *prometheus.Desc 26 | tempCriticalThresholdDesc *prometheus.Desc 27 | tempFatalThresholdDesc *prometheus.Desc 28 | tempHealthyDesc *prometheus.Desc 29 | ) 30 | 31 | func init() { 32 | l := []string{"host", "name"} 33 | 34 | fanHealthyDesc = prometheus.NewDesc(prefix+"fan_healthy", "Health status of the fan", l, nil) 35 | fanEnabledDesc = prometheus.NewDesc(prefix+"fan_enabled", "Status of the fan", l, nil) 36 | fanCurrentDesc = prometheus.NewDesc(prefix+"fan_current_percent", "Current power in percent", l, nil) 37 | tempCurrentDesc = prometheus.NewDesc(prefix+"temperature_current", "Current temperature in degree celsius", l, nil) 38 | tempCriticalThresholdDesc = prometheus.NewDesc(prefix+"temperature_critical", "Critcal temperature threshold in degree celsius", l, nil) 39 | tempFatalThresholdDesc = prometheus.NewDesc(prefix+"temperature_fatal", "Fatal temperature threshold in degree celsius", l, nil) 40 | tempHealthyDesc = prometheus.NewDesc(prefix+"temperature_healthy", "Health status of the temperature sensor", l, nil) 41 | } 42 | 43 | func Describe(ch chan<- *prometheus.Desc) { 44 | ch <- fanHealthyDesc 45 | ch <- fanEnabledDesc 46 | ch <- fanCurrentDesc 47 | ch <- tempCurrentDesc 48 | ch <- tempCriticalThresholdDesc 49 | ch <- tempFatalThresholdDesc 50 | ch <- tempHealthyDesc 51 | } 52 | 53 | func Collect(ctx context.Context, parentPath string, cc *common.CollectorContext) { 54 | ctx, span := cc.Tracer().Start(ctx, "Thermal.Collect", trace.WithAttributes( 55 | attribute.String("parent_path", parentPath), 56 | )) 57 | defer span.End() 58 | 59 | th := Thermal{} 60 | err := cc.Client().Get(ctx, parentPath+"/Thermal", &th) 61 | if err != nil { 62 | cc.HandleError(fmt.Errorf("could not get thermal data: %w", err), span) 63 | } 64 | 65 | hostname := cc.Client().HostName() 66 | for _, f := range th.Fans { 67 | if f.Status.State == "UnavailableOffline" || f.Status.State == "Offline" { 68 | continue 69 | } 70 | 71 | collectForFan(hostname, &f, cc) 72 | } 73 | 74 | for _, t := range th.Temperatures { 75 | if t.Status.State == "Absent" || t.Status.State == "Offline" { 76 | continue 77 | } 78 | 79 | collectForTemperature(hostname, &t, cc) 80 | } 81 | } 82 | 83 | func collectForFan(hostName string, f *Fan, cc *common.CollectorContext) { 84 | l := []string{hostName, f.Name()} 85 | cc.RecordMetrics( 86 | prometheus.MustNewConstMetric(fanHealthyDesc, prometheus.GaugeValue, f.Status.HealthValue(), l...), 87 | prometheus.MustNewConstMetric(fanEnabledDesc, prometheus.GaugeValue, f.Status.EnabledValue(), l...), 88 | prometheus.MustNewConstMetric(fanCurrentDesc, prometheus.GaugeValue, f.Reading(), l...), 89 | ) 90 | } 91 | 92 | func collectForTemperature(hostName string, t *Temperature, cc *common.CollectorContext) { 93 | l := []string{hostName, t.Name} 94 | cc.RecordMetrics( 95 | prometheus.MustNewConstMetric(tempCurrentDesc, prometheus.GaugeValue, t.ReadingCelsius, l...), 96 | prometheus.MustNewConstMetric(tempCriticalThresholdDesc, prometheus.GaugeValue, t.UpperThresholdCritical, l...), 97 | prometheus.MustNewConstMetric(tempFatalThresholdDesc, prometheus.GaugeValue, t.UpperThresholdFatal, l...), 98 | prometheus.MustNewConstMetric(tempHealthyDesc, prometheus.GaugeValue, t.Status.HealthValue(), l...), 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /pkg/chassis/thermal/temperature.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package thermal 6 | 7 | import ( 8 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 9 | ) 10 | 11 | type Temperature struct { 12 | Name string `json:"Name"` 13 | ReadingCelsius float64 `json:"ReadingCelsius"` 14 | UpperThresholdCritical float64 `json:"UpperThresholdCritical"` 15 | UpperThresholdFatal float64 `json:"UpperThresholdFatal"` 16 | Status common.Status `json:"Status"` 17 | } 18 | -------------------------------------------------------------------------------- /pkg/chassis/thermal/thermal.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package thermal 6 | 7 | type Thermal struct { 8 | Temperatures []Temperature `json:"Temperatures"` 9 | Fans []Fan `json:"Fans"` 10 | } 11 | -------------------------------------------------------------------------------- /pkg/client/api_client.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package client 6 | 7 | import ( 8 | "context" 9 | "crypto/tls" 10 | "encoding/json" 11 | "fmt" 12 | "io" 13 | "net/http" 14 | "strings" 15 | 16 | "go.opentelemetry.io/otel/attribute" 17 | "go.opentelemetry.io/otel/codes" 18 | "go.opentelemetry.io/otel/trace" 19 | "golang.org/x/sync/semaphore" 20 | 21 | "github.com/sirupsen/logrus" 22 | ) 23 | 24 | // APIClient communicates with the API and parses the results 25 | type APIClient struct { 26 | url string 27 | hostName string 28 | username string 29 | password string 30 | tracer trace.Tracer 31 | client *http.Client 32 | debug bool 33 | sem *semaphore.Weighted 34 | } 35 | 36 | // ClientOption applies options to APIClient 37 | type ClientOption func(*APIClient) 38 | 39 | // WithInsecure disables TLS certificate validation 40 | func WithInsecure() ClientOption { 41 | return func(c *APIClient) { 42 | tr := &http.Transport{ 43 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 44 | } 45 | c.client = &http.Client{Transport: tr} 46 | } 47 | } 48 | 49 | // WithDebug enables debug mode 50 | func WithDebug() ClientOption { 51 | return func(c *APIClient) { 52 | c.debug = true 53 | } 54 | } 55 | 56 | // WithMaxConcurrentRequests defines the maximum number of GET requests sent against API concurrently 57 | func WithMaxConcurrentRequests(max uint) ClientOption { 58 | return func(c *APIClient) { 59 | c.sem = semaphore.NewWeighted(int64(max)) 60 | } 61 | } 62 | 63 | // NewClient creates a new client instance 64 | func NewClient(hostName, username, password string, tracer trace.Tracer, opts ...ClientOption) Client { 65 | cl := &APIClient{ 66 | url: fmt.Sprintf("https://%s/redfish/v1/", hostName), 67 | hostName: hostName, 68 | username: username, 69 | password: password, 70 | client: &http.Client{}, 71 | tracer: tracer, 72 | sem: semaphore.NewWeighted(1), 73 | } 74 | 75 | for _, o := range opts { 76 | o(cl) 77 | } 78 | 79 | return cl 80 | } 81 | 82 | // HostName returns the name of the host 83 | func (cl *APIClient) HostName() string { 84 | return cl.hostName 85 | } 86 | 87 | // Get retrieves the ressource from the API and unmashals the json retrieved 88 | func (cl *APIClient) Get(ctx context.Context, path string, obj interface{}) error { 89 | path = strings.Replace(path, "/redfish/v1/", "", 1) 90 | 91 | b, err := cl.get(ctx, path) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | err = json.Unmarshal(b, obj) 97 | return err 98 | } 99 | 100 | func (cl *APIClient) get(ctx context.Context, path string) ([]byte, error) { 101 | cl.sem.Acquire(context.Background(), 1) 102 | defer cl.sem.Release(1) 103 | 104 | uri := strings.Trim(cl.url, "/") + "/" + strings.Trim(path, "/") 105 | 106 | _, span := cl.tracer.Start(ctx, "Client.Get", trace.WithAttributes( 107 | attribute.String("URI", uri), 108 | )) 109 | defer span.End() 110 | 111 | logrus.Infof("GET %s", uri) 112 | 113 | req, err := http.NewRequest("GET", uri, nil) 114 | if err != nil { 115 | span.RecordError(err) 116 | span.SetStatus(codes.Error, err.Error()) 117 | return nil, err 118 | } 119 | 120 | req.SetBasicAuth(cl.username, cl.password) 121 | req.Header.Add("Content-Type", "application/json") 122 | 123 | resp, err := cl.client.Do(req) 124 | if err != nil { 125 | span.RecordError(err) 126 | span.SetStatus(codes.Error, err.Error()) 127 | return nil, err 128 | } 129 | defer resp.Body.Close() 130 | 131 | if resp.StatusCode >= 300 { 132 | span.RecordError(err) 133 | span.SetStatus(codes.Error, resp.Status) 134 | return nil, fmt.Errorf("%s", resp.Status) 135 | } 136 | 137 | b, err := io.ReadAll(resp.Body) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | if cl.debug { 143 | logrus.Infof("Status Code: %s", resp.Status) 144 | logrus.Infof("Response: %s", string(b)) 145 | } 146 | 147 | span.SetStatus(codes.Ok, "") 148 | 149 | return b, err 150 | } 151 | -------------------------------------------------------------------------------- /pkg/client/client.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package client 6 | 7 | import "context" 8 | 9 | type Client interface { 10 | HostName() string 11 | Get(ctx context.Context, ressource string, obj interface{}) error 12 | } 13 | -------------------------------------------------------------------------------- /pkg/client/dunmy_client.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package client 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | ) 11 | 12 | type DummyClient struct { 13 | values map[string]string 14 | } 15 | 16 | // NewDummy returns a new dummy client 17 | func NewDummy() *DummyClient { 18 | return &DummyClient{ 19 | values: make(map[string]string), 20 | } 21 | } 22 | 23 | func (cl *DummyClient) HostName() string { 24 | return "Dummy" 25 | } 26 | 27 | // SetResponse sets an dummy response for an ressource path 28 | func (cl *DummyClient) SetResponse(ressource string, value string) { 29 | cl.values[ressource] = value 30 | } 31 | 32 | // Get parses the dummy string for an given ressource and unmarshals the json 33 | func (cl *DummyClient) Get(context context.Context, ressource string, obj interface{}) error { 34 | return json.Unmarshal([]byte(cl.values[ressource]), obj) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/common/collector_context.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package common 6 | 7 | import ( 8 | "context" 9 | "sync" 10 | "sync/atomic" 11 | 12 | "github.com/MauveSoftware/ilo_exporter/pkg/client" 13 | "github.com/prometheus/client_golang/prometheus" 14 | "github.com/sirupsen/logrus" 15 | "go.opentelemetry.io/otel/codes" 16 | "go.opentelemetry.io/otel/trace" 17 | ) 18 | 19 | func NewCollectorContext(ctx context.Context, cl client.Client, ch chan<- prometheus.Metric, tracer trace.Tracer) *CollectorContext { 20 | return &CollectorContext{ 21 | rootCtx: ctx, 22 | cl: cl, 23 | ch: ch, 24 | tracer: tracer, 25 | wg: &sync.WaitGroup{}, 26 | errCount: 0, 27 | } 28 | } 29 | 30 | type CollectorContext struct { 31 | rootCtx context.Context 32 | wg *sync.WaitGroup 33 | ch chan<- prometheus.Metric 34 | tracer trace.Tracer 35 | cl client.Client 36 | errCount int32 37 | } 38 | 39 | func (cc *CollectorContext) Client() client.Client { 40 | return cc.cl 41 | } 42 | 43 | func (cc *CollectorContext) RootCtx() context.Context { 44 | return cc.rootCtx 45 | } 46 | 47 | func (cc *CollectorContext) WaitGroup() *sync.WaitGroup { 48 | return cc.wg 49 | } 50 | 51 | func (cc *CollectorContext) Tracer() trace.Tracer { 52 | return cc.tracer 53 | } 54 | 55 | func (cc *CollectorContext) RecordMetrics(metrics ...prometheus.Metric) { 56 | for _, m := range metrics { 57 | cc.ch <- m 58 | } 59 | } 60 | 61 | func (cc *CollectorContext) ErrCount() int32 { 62 | return cc.errCount 63 | } 64 | 65 | func (cc *CollectorContext) HandleError(err error, span trace.Span) { 66 | atomic.AddInt32(&cc.errCount, 1) 67 | span.RecordError(err) 68 | span.SetStatus(codes.Error, err.Error()) 69 | logrus.Error(err.Error()) 70 | } 71 | -------------------------------------------------------------------------------- /pkg/common/member_list.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package common 6 | 7 | type MemberList struct { 8 | Members []Member 9 | } 10 | 11 | type Member struct { 12 | Path string `json:"@odata.id"` 13 | } 14 | -------------------------------------------------------------------------------- /pkg/common/status.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package common 6 | 7 | import ( 8 | "strings" 9 | ) 10 | 11 | type Status struct { 12 | Health string `json:"Health"` 13 | State string `json:"State"` 14 | } 15 | 16 | func (s *Status) HealthValue() float64 { 17 | if strings.ToUpper(s.Health) == "OK" { 18 | return 1 19 | } 20 | 21 | return 0 22 | } 23 | 24 | func (s *Status) EnabledValue() float64 { 25 | if s.State == "Enabled" { 26 | return 1 27 | } 28 | 29 | return 0 30 | } 31 | -------------------------------------------------------------------------------- /pkg/manager/collector.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package manager 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "github.com/MauveSoftware/ilo_exporter/pkg/client" 12 | "github.com/prometheus/client_golang/prometheus" 13 | "github.com/sirupsen/logrus" 14 | "go.opentelemetry.io/otel/trace" 15 | ) 16 | 17 | const ( 18 | prefix = "ilo_manager_" 19 | ) 20 | 21 | var ( 22 | infoDesc = prometheus.NewDesc(prefix+"info", "System Info", []string{"host", "firmware_version"}, nil) 23 | scrapeDurationDesc = prometheus.NewDesc(prefix+"scrape_duration_second", "Scrape duration for the manager module", []string{"host"}, nil) 24 | ) 25 | 26 | // NewCollector returns a new collector for manager metrics 27 | func NewCollector(ctx context.Context, cl client.Client, tracer trace.Tracer) prometheus.Collector { 28 | return &collector{ 29 | rootCtx: ctx, 30 | cl: cl, 31 | tracer: tracer, 32 | } 33 | } 34 | 35 | type collector struct { 36 | rootCtx context.Context 37 | cl client.Client 38 | tracer trace.Tracer 39 | } 40 | 41 | // Describe implements prometheus.Collector interface 42 | func (c *collector) Describe(ch chan<- *prometheus.Desc) { 43 | ch <- infoDesc 44 | ch <- scrapeDurationDesc 45 | } 46 | 47 | // Collect implements prometheus.Collector interface 48 | func (c *collector) Collect(ch chan<- prometheus.Metric) { 49 | start := time.Now() 50 | 51 | ctx, span := c.tracer.Start(c.rootCtx, "Manager.Collect") 52 | defer span.End() 53 | 54 | p := "Managers/1" 55 | 56 | m := &manager{} 57 | err := c.cl.Get(ctx, p, &m) 58 | if err != nil { 59 | logrus.Error(err) 60 | return 61 | } 62 | 63 | duration := time.Since(start).Seconds() 64 | ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, duration, c.cl.HostName()) 65 | ch <- prometheus.MustNewConstMetric(infoDesc, prometheus.GaugeValue, 1, c.cl.HostName(), m.FirmwareVersion) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/manager/manager.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package manager 6 | 7 | type manager struct { 8 | FirmwareVersion string `json:"FirmwareVersion"` 9 | } 10 | -------------------------------------------------------------------------------- /pkg/system/collector.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package system 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "github.com/MauveSoftware/ilo_exporter/pkg/client" 12 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 13 | "github.com/MauveSoftware/ilo_exporter/pkg/system/memory" 14 | "github.com/MauveSoftware/ilo_exporter/pkg/system/processor" 15 | "github.com/MauveSoftware/ilo_exporter/pkg/system/storage" 16 | "github.com/prometheus/client_golang/prometheus" 17 | "github.com/sirupsen/logrus" 18 | "go.opentelemetry.io/otel/trace" 19 | ) 20 | 21 | const ( 22 | prefix = "ilo_" 23 | ) 24 | 25 | var ( 26 | powerUpDesc = prometheus.NewDesc(prefix+"power_up", "Power status", []string{"host"}, nil) 27 | infoDesc = prometheus.NewDesc(prefix+"system_info", "System Info", []string{"host", "uuid", "serial", "sku", "model", "host_name", "bios_version"}, nil) 28 | scrapeDurationDesc = prometheus.NewDesc(prefix+"system_scrape_duration_second", "Scrape duration for the system module", []string{"host"}, nil) 29 | ) 30 | 31 | // NewCollector returns a new collector for system metrics 32 | func NewCollector(ctx context.Context, cl client.Client, tracer trace.Tracer) prometheus.Collector { 33 | return &collector{ 34 | rootCtx: ctx, 35 | cl: cl, 36 | tracer: tracer, 37 | } 38 | } 39 | 40 | type collector struct { 41 | rootCtx context.Context 42 | cl client.Client 43 | tracer trace.Tracer 44 | } 45 | 46 | // Describe implements prometheus.Collector interface 47 | func (c *collector) Describe(ch chan<- *prometheus.Desc) { 48 | ch <- powerUpDesc 49 | ch <- infoDesc 50 | ch <- scrapeDurationDesc 51 | memory.Describe(ch) 52 | processor.Describe(ch) 53 | storage.Describe(ch) 54 | } 55 | 56 | // Collect implements prometheus.Collector interface 57 | func (c *collector) Collect(ch chan<- prometheus.Metric) { 58 | start := time.Now() 59 | 60 | ctx, span := c.tracer.Start(c.rootCtx, "System.Collect") 61 | defer span.End() 62 | 63 | p := "Systems/1" 64 | 65 | s := System{} 66 | err := c.cl.Get(ctx, p, &s) 67 | if err != nil { 68 | logrus.Error(err) 69 | } 70 | 71 | ch <- prometheus.MustNewConstMetric(powerUpDesc, prometheus.GaugeValue, s.PowerUpValue(), c.cl.HostName()) 72 | ch <- prometheus.MustNewConstMetric(infoDesc, prometheus.GaugeValue, 1, c.cl.HostName(), 73 | s.UUID, s.SerialNumber, s.SKU, s.Model, s.HostName, s.BiosVersion) 74 | 75 | doneCh := make(chan interface{}) 76 | 77 | cc := common.NewCollectorContext(ctx, c.cl, ch, c.tracer) 78 | 79 | cc.WaitGroup().Add(3) 80 | go func() { 81 | cc.WaitGroup().Wait() 82 | doneCh <- nil 83 | }() 84 | 85 | go memory.Collect(p, cc) 86 | go processor.Collect(p, cc) 87 | go storage.Collect(p, cc) 88 | 89 | <-doneCh 90 | if cc.ErrCount() == 0 { 91 | duration := time.Since(start).Seconds() 92 | ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, duration, c.cl.HostName()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/system/memory/memory_dimm.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package memory 6 | 7 | import "github.com/MauveSoftware/ilo_exporter/pkg/common" 8 | 9 | type MemoryDIMM struct { 10 | Name string `json:"Name"` 11 | StatusCurrent common.Status `json:"Status"` 12 | StatusLegacy string `json:"DIMMStatus"` 13 | SizeMBCurrent uint64 `json:"CapacityMiB"` 14 | SizeMBLegacy uint64 `json:"SizeMB"` 15 | } 16 | 17 | func (m *MemoryDIMM) isLegacy() bool { 18 | return len(m.StatusLegacy) > 0 19 | } 20 | 21 | func (m *MemoryDIMM) legacyHealthValue() float64 { 22 | if m.StatusLegacy == "GoodInUse" { 23 | return 1 24 | } 25 | 26 | return 0 27 | } 28 | 29 | func (m *MemoryDIMM) IsValid() bool { 30 | if m.isLegacy() { 31 | return m.StatusLegacy != "Unknown" 32 | } 33 | 34 | return len(m.StatusCurrent.State) > 0 35 | } 36 | 37 | func (m *MemoryDIMM) HealthValue() float64 { 38 | if m.isLegacy() { 39 | return m.legacyHealthValue() 40 | } 41 | 42 | return m.StatusCurrent.HealthValue() 43 | } 44 | 45 | func (m *MemoryDIMM) SizeMB() uint64 { 46 | if m.isLegacy() { 47 | return m.SizeMBLegacy 48 | } 49 | 50 | return m.SizeMBCurrent 51 | } 52 | -------------------------------------------------------------------------------- /pkg/system/memory/metrics.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package memory 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "go.opentelemetry.io/otel/attribute" 13 | "go.opentelemetry.io/otel/trace" 14 | 15 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 16 | "github.com/prometheus/client_golang/prometheus" 17 | ) 18 | 19 | const ( 20 | prefix = "ilo_memory_" 21 | ) 22 | 23 | var ( 24 | healthyDesc *prometheus.Desc 25 | totalMemory *prometheus.Desc 26 | dimmHealthyDesc *prometheus.Desc 27 | dimmSizeDesc *prometheus.Desc 28 | ) 29 | 30 | func init() { 31 | l := []string{"host"} 32 | healthyDesc = prometheus.NewDesc(prefix+"healthy", "Health status of the memory", l, nil) 33 | totalMemory = prometheus.NewDesc(prefix+"total_byte", "Total memory installed in bytes", l, nil) 34 | 35 | l = append(l, "name") 36 | dimmHealthyDesc = prometheus.NewDesc(prefix+"dimm_healthy", "Health status of processor", l, nil) 37 | dimmSizeDesc = prometheus.NewDesc(prefix+"dimm_byte", "DIMM size in bytes", l, nil) 38 | } 39 | 40 | // Describe describes all metrics for the memory package 41 | func Describe(ch chan<- *prometheus.Desc) { 42 | ch <- healthyDesc 43 | ch <- totalMemory 44 | ch <- dimmHealthyDesc 45 | ch <- dimmSizeDesc 46 | } 47 | 48 | // Collect collects metrics for memory modules 49 | func Collect(parentPath string, cc *common.CollectorContext) { 50 | defer cc.WaitGroup().Done() 51 | 52 | ctx, span := cc.Tracer().Start(cc.RootCtx(), "Memory.Collect", trace.WithAttributes( 53 | attribute.String("parent_path", parentPath), 54 | )) 55 | defer span.End() 56 | 57 | p := parentPath + "/Memory" 58 | mem := common.MemberList{} 59 | 60 | err := cc.Client().Get(ctx, p, &mem) 61 | if err != nil { 62 | cc.HandleError(fmt.Errorf("could not get memory summary: %w", err), span) 63 | return 64 | } 65 | 66 | cc.WaitGroup().Add(len(mem.Members)) 67 | 68 | for _, m := range mem.Members { 69 | go collectForDIMM(ctx, m.Path, cc) 70 | } 71 | } 72 | 73 | func collectForDIMM(ctx context.Context, link string, cc *common.CollectorContext) { 74 | defer cc.WaitGroup().Done() 75 | 76 | ctx, span := cc.Tracer().Start(ctx, "Memory.CollectForDIMM", trace.WithAttributes( 77 | attribute.String("path", link), 78 | )) 79 | defer span.End() 80 | 81 | i := strings.Index(link, "Systems/") 82 | p := link[i:] 83 | 84 | d := MemoryDIMM{} 85 | err := cc.Client().Get(ctx, p, &d) 86 | if err != nil { 87 | cc.HandleError(fmt.Errorf("could not get memory information from %s: %w", link, err), span) 88 | return 89 | } 90 | 91 | l := []string{cc.Client().HostName(), d.Name} 92 | 93 | if !d.IsValid() { 94 | return 95 | } 96 | 97 | cc.RecordMetrics( 98 | prometheus.MustNewConstMetric(dimmHealthyDesc, prometheus.GaugeValue, d.HealthValue(), l...), 99 | prometheus.MustNewConstMetric(dimmSizeDesc, prometheus.GaugeValue, float64(d.SizeMB()<<20), l...), 100 | ) 101 | } 102 | -------------------------------------------------------------------------------- /pkg/system/processor/metrics.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package processor 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | 12 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 13 | "github.com/prometheus/client_golang/prometheus" 14 | "go.opentelemetry.io/otel/attribute" 15 | "go.opentelemetry.io/otel/trace" 16 | ) 17 | 18 | const ( 19 | prefix = "ilo_processor_" 20 | ) 21 | 22 | var ( 23 | countDesc *prometheus.Desc 24 | coresDesc *prometheus.Desc 25 | threadsDesc *prometheus.Desc 26 | healthyDesc *prometheus.Desc 27 | ) 28 | 29 | func init() { 30 | l := []string{"host"} 31 | countDesc = prometheus.NewDesc(prefix+"count", "Number of processors", l, nil) 32 | 33 | l = append(l, "socket", "model") 34 | coresDesc = prometheus.NewDesc(prefix+"core_count", "Number of cores of processor", l, nil) 35 | threadsDesc = prometheus.NewDesc(prefix+"thread_count", "Number of threads of processor", l, nil) 36 | healthyDesc = prometheus.NewDesc(prefix+"healthy", "Health status of processor", l, nil) 37 | } 38 | 39 | // Describe describes all metrics for the processor package 40 | func Describe(ch chan<- *prometheus.Desc) { 41 | ch <- countDesc 42 | ch <- coresDesc 43 | ch <- threadsDesc 44 | ch <- healthyDesc 45 | } 46 | 47 | // Collect collects processor metrics 48 | func Collect(parentPath string, cc *common.CollectorContext) { 49 | defer cc.WaitGroup().Done() 50 | 51 | ctx, span := cc.Tracer().Start(cc.RootCtx(), "Processor.Collect", trace.WithAttributes( 52 | attribute.String("parent_path", parentPath), 53 | )) 54 | defer span.End() 55 | 56 | p := parentPath + "/Processors" 57 | procs := common.MemberList{} 58 | err := cc.Client().Get(ctx, p, &procs) 59 | if err != nil { 60 | cc.HandleError(fmt.Errorf("could not get processor summary: %w", err), span) 61 | return 62 | } 63 | 64 | cc.RecordMetrics( 65 | prometheus.MustNewConstMetric(countDesc, prometheus.GaugeValue, float64(len(procs.Members)), cc.Client().HostName()), 66 | ) 67 | 68 | for _, l := range procs.Members { 69 | collectForProcessor(ctx, l.Path, cc) 70 | } 71 | } 72 | 73 | func collectForProcessor(ctx context.Context, link string, cc *common.CollectorContext) { 74 | ctx, span := cc.Tracer().Start(ctx, "Storage.CollectProcessor", trace.WithAttributes( 75 | attribute.String("path", link), 76 | )) 77 | defer span.End() 78 | 79 | pr := Processor{} 80 | err := cc.Client().Get(ctx, link, &pr) 81 | if err != nil { 82 | cc.HandleError(fmt.Errorf("could not get processor information from %s: %w", link, err), span) 83 | return 84 | } 85 | 86 | l := []string{cc.Client().HostName(), pr.Socket, strings.Trim(pr.Model, " ")} 87 | cc.RecordMetrics( 88 | prometheus.MustNewConstMetric(coresDesc, prometheus.GaugeValue, pr.TotalCores, l...), 89 | prometheus.MustNewConstMetric(threadsDesc, prometheus.GaugeValue, pr.TotalThreads, l...), 90 | prometheus.MustNewConstMetric(healthyDesc, prometheus.GaugeValue, pr.Status.HealthValue(), l...), 91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /pkg/system/processor/processor.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package processor 6 | 7 | import "github.com/MauveSoftware/ilo_exporter/pkg/common" 8 | 9 | type Processor struct { 10 | Socket string `json:"Socket"` 11 | Model string `json:"Model"` 12 | TotalCores float64 `json:"TotalCores"` 13 | TotalThreads float64 `json:"TotalThreads"` 14 | Status common.Status `json:"Status"` 15 | } 16 | -------------------------------------------------------------------------------- /pkg/system/storage/disk_drive.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package storage 6 | 7 | import ( 8 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 9 | ) 10 | 11 | type DiskDrive struct { 12 | MediaType string `json:"MediaType"` 13 | Model string `json:"Model"` 14 | Location Location `json:"Location"` 15 | CapacityB uint64 `json:"CapacityBytes"` 16 | CapacityMB uint64 `json:"CapacityMiB"` 17 | Status common.Status `json:"Status"` 18 | } 19 | 20 | func (d *DiskDrive) CapacityBytes() float64 { 21 | if d.CapacityMB > 0 { 22 | return float64(d.CapacityMB << 10) 23 | } 24 | 25 | return float64(d.CapacityB) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/system/storage/location.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package storage 6 | 7 | import "encoding/json" 8 | 9 | type Location string 10 | 11 | type LocationInfo struct { 12 | Info string `json:"Info"` 13 | } 14 | 15 | func (f *Location) UnmarshalJSON(data []byte) error { 16 | var infos []LocationInfo 17 | json.Unmarshal(data, &infos) 18 | 19 | if len(infos) > 0 { 20 | *f = Location(infos[0].Info) 21 | return nil 22 | } 23 | 24 | var str string 25 | json.Unmarshal(data, &str) 26 | *f = Location(str) 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/system/storage/metrics.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package storage 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "github.com/MauveSoftware/ilo_exporter/pkg/common" 12 | "github.com/prometheus/client_golang/prometheus" 13 | "go.opentelemetry.io/otel/attribute" 14 | "go.opentelemetry.io/otel/trace" 15 | ) 16 | 17 | const ( 18 | prefix = "ilo_storage_" 19 | ) 20 | 21 | var ( 22 | diskDriveCapacityDesc *prometheus.Desc 23 | diskDriveHealthyDesc *prometheus.Desc 24 | ) 25 | 26 | func init() { 27 | dl := []string{"host", "location", "model", "media_type"} 28 | diskDriveCapacityDesc = prometheus.NewDesc(prefix+"disk_capacity_byte", "Capacity of the disk in bytes", dl, nil) 29 | diskDriveHealthyDesc = prometheus.NewDesc(prefix+"disk_healthy", "Health status of the diks", dl, nil) 30 | } 31 | 32 | // Describe describes all metrics for the storage package 33 | func Describe(ch chan<- *prometheus.Desc) { 34 | ch <- diskDriveCapacityDesc 35 | ch <- diskDriveHealthyDesc 36 | } 37 | 38 | // Collect collects metrics for storage controllers 39 | func Collect(parentPath string, cc *common.CollectorContext) { 40 | defer cc.WaitGroup().Done() 41 | 42 | ctx, span := cc.Tracer().Start(cc.RootCtx(), "Storage.Collect", trace.WithAttributes( 43 | attribute.String("parent_path", parentPath), 44 | )) 45 | defer span.End() 46 | 47 | collectStorage(ctx, parentPath, cc) 48 | collectSmartStorage(ctx, parentPath, cc) 49 | } 50 | 51 | func collectStorage(ctx context.Context, parentPath string, cc *common.CollectorContext) { 52 | ctx, span := cc.Tracer().Start(ctx, "Storage.CollectStorage", trace.WithAttributes( 53 | attribute.String("parent_path", parentPath), 54 | )) 55 | defer span.End() 56 | 57 | p := parentPath + "/Storage" 58 | crtls := common.MemberList{} 59 | 60 | err := cc.Client().Get(ctx, p, &crtls) 61 | if err != nil { 62 | cc.HandleError(fmt.Errorf("could not get storage controller summary: %w", err), span) 63 | return 64 | } 65 | 66 | for _, l := range crtls.Members { 67 | collectStorageController(ctx, l.Path, cc) 68 | } 69 | } 70 | 71 | func collectStorageController(ctx context.Context, path string, cc *common.CollectorContext) { 72 | ctx, span := cc.Tracer().Start(ctx, "Storage.CollectController", trace.WithAttributes( 73 | attribute.String("path", path), 74 | )) 75 | defer span.End() 76 | 77 | strg := StorageInfo{} 78 | err := cc.Client().Get(ctx, path, &strg) 79 | if err != nil { 80 | cc.HandleError(fmt.Errorf("could not get storage controller summary: %w", err), span) 81 | return 82 | } 83 | 84 | for _, drv := range strg.Drives { 85 | collectDiskDrive(ctx, drv.Path, cc) 86 | } 87 | } 88 | 89 | func collectSmartStorage(ctx context.Context, parentPath string, cc *common.CollectorContext) { 90 | ctx, span := cc.Tracer().Start(ctx, "Storage.CollectSmartStorage", trace.WithAttributes( 91 | attribute.String("parent_path", parentPath), 92 | )) 93 | defer span.End() 94 | 95 | p := parentPath + "/SmartStorage/ArrayControllers/" 96 | crtls := common.MemberList{} 97 | err := cc.Client().Get(ctx, p, &crtls) 98 | if err != nil { 99 | cc.HandleError(fmt.Errorf("could not get smart storage controller summary: %w", err), span) 100 | return 101 | } 102 | 103 | for _, m := range crtls.Members { 104 | collectSmartStorageController(ctx, m.Path, cc) 105 | } 106 | } 107 | 108 | func collectSmartStorageController(ctx context.Context, path string, cc *common.CollectorContext) { 109 | ctx, span := cc.Tracer().Start(ctx, "Storage.CollectSmartController", trace.WithAttributes( 110 | attribute.String("path", path), 111 | )) 112 | defer span.End() 113 | 114 | p := path + "DiskDrives/" 115 | drives := common.MemberList{} 116 | err := cc.Client().Get(ctx, p, &drives) 117 | if err != nil { 118 | cc.HandleError(fmt.Errorf("could not get drives for controller %s: %w", path, err), span) 119 | return 120 | } 121 | 122 | for _, drv := range drives.Members { 123 | collectDiskDrive(ctx, drv.Path, cc) 124 | } 125 | } 126 | 127 | func collectDiskDrive(ctx context.Context, path string, cc *common.CollectorContext) { 128 | ctx, span := cc.Tracer().Start(ctx, "Storage.CollectDisk", trace.WithAttributes( 129 | attribute.String("path", path), 130 | )) 131 | defer span.End() 132 | 133 | d := DiskDrive{} 134 | err := cc.Client().Get(ctx, path, &d) 135 | if err != nil { 136 | cc.HandleError(fmt.Errorf("could not get drive information from %s: %w", path, err), span) 137 | return 138 | } 139 | 140 | l := []string{cc.Client().HostName(), string(d.Location), d.Model, d.MediaType} 141 | cc.RecordMetrics( 142 | prometheus.MustNewConstMetric(diskDriveCapacityDesc, prometheus.GaugeValue, float64(d.CapacityBytes()), l...), 143 | prometheus.MustNewConstMetric(diskDriveHealthyDesc, prometheus.GaugeValue, d.Status.HealthValue(), l...), 144 | ) 145 | } 146 | -------------------------------------------------------------------------------- /pkg/system/storage/storage_info.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package storage 6 | 7 | import "github.com/MauveSoftware/ilo_exporter/pkg/common" 8 | 9 | type StorageInfo struct { 10 | Drives []common.Member `json:"Drives"` 11 | } 12 | -------------------------------------------------------------------------------- /pkg/system/system.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package system 6 | 7 | type System struct { 8 | PowerState string `json:"PowerState"` 9 | UUID string `json:"UUID"` 10 | SerialNumber string `json:"SerialNumber"` 11 | SKU string `json:"SKU"` 12 | Model string `json:"Model"` 13 | HostName string `json:"HostName"` 14 | BiosVersion string `json:"BiosVersion"` 15 | } 16 | 17 | func (s *System) PowerUpValue() float64 { 18 | if s.PowerState == "On" { 19 | return 1 20 | } 21 | 22 | return 0 23 | } 24 | -------------------------------------------------------------------------------- /tracing.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (c) Mauve Mailorder Software GmbH & Co. KG, 2022. Licensed under [MIT](LICENSE) license. 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | log "github.com/sirupsen/logrus" 12 | "go.opentelemetry.io/otel" 13 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace" 14 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" 15 | "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 16 | "go.opentelemetry.io/otel/propagation" 17 | "go.opentelemetry.io/otel/sdk/resource" 18 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 19 | semconv "go.opentelemetry.io/otel/semconv/v1.17.0" 20 | "go.opentelemetry.io/otel/trace" 21 | ) 22 | 23 | var ( 24 | tracer = otel.GetTracerProvider().Tracer( 25 | "github.com/czerwonk/ilo_exporter", 26 | trace.WithSchemaURL(semconv.SchemaURL), 27 | ) 28 | ) 29 | 30 | func initTracing(ctx context.Context) (func(), error) { 31 | if !*tracingEnabled { 32 | return initTracingWithNoop() 33 | } 34 | 35 | switch *tracingProvider { 36 | case "stdout": 37 | return initTracingToStdOut(ctx) 38 | case "collector": 39 | return initTracingToCollector(ctx) 40 | default: 41 | log.Warnf("got invalid value for tracing.provider: %s, disable tracing", *tracingProvider) 42 | return initTracingWithNoop() 43 | } 44 | } 45 | 46 | func initTracingWithNoop() (func(), error) { 47 | tp := trace.NewNoopTracerProvider() 48 | otel.SetTracerProvider(tp) 49 | 50 | return func() {}, nil 51 | } 52 | 53 | func initTracingToStdOut(ctx context.Context) (func(), error) { 54 | log.Info("Initialize tracing (STDOUT)") 55 | 56 | exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) 57 | if err != nil { 58 | return nil, fmt.Errorf("failed to create stdout exporter: %w", err) 59 | } 60 | 61 | tp := sdktrace.NewTracerProvider( 62 | sdktrace.WithBatcher(exp), 63 | sdktrace.WithResource(resourceDefinition()), 64 | ) 65 | otel.SetTracerProvider(tp) 66 | 67 | return shutdownTraceProvider(ctx, tp.Shutdown), nil 68 | } 69 | 70 | func initTracingToCollector(ctx context.Context) (func(), error) { 71 | log.Infof("Initialize tracing (agent: %s)", *tracingCollectorEndpoint) 72 | 73 | cl := otlptracegrpc.NewClient( 74 | otlptracegrpc.WithInsecure(), 75 | otlptracegrpc.WithEndpoint(*tracingCollectorEndpoint), 76 | ) 77 | exp, err := otlptrace.New(ctx, cl) 78 | if err != nil { 79 | return nil, fmt.Errorf("failed to create gRPC collector exporter: %w", err) 80 | } 81 | 82 | bsp := sdktrace.NewBatchSpanProcessor(exp) 83 | tp := sdktrace.NewTracerProvider( 84 | sdktrace.WithSampler(sdktrace.AlwaysSample()), 85 | sdktrace.WithResource(resourceDefinition()), 86 | sdktrace.WithSpanProcessor(bsp), 87 | ) 88 | otel.SetTracerProvider(tp) 89 | otel.SetTextMapPropagator(propagation.TraceContext{}) 90 | 91 | return shutdownTraceProvider(ctx, tp.Shutdown), nil 92 | } 93 | 94 | func shutdownTraceProvider(ctx context.Context, shutdownFunc func(ctx context.Context) error) func() { 95 | return func() { 96 | if err := shutdownFunc(ctx); err != nil { 97 | log.Errorf("failed to shutdown TracerProvider: %v", err) 98 | } 99 | } 100 | } 101 | 102 | func resourceDefinition() *resource.Resource { 103 | return resource.NewWithAttributes( 104 | semconv.SchemaURL, 105 | semconv.ServiceNameKey.String("ilo_exporter"), 106 | semconv.ServiceVersionKey.String(version), 107 | ) 108 | } 109 | --------------------------------------------------------------------------------