Added parity status metrics
Signed-off-by: Marcus Noble <github@marcusnoble.co.uk>
This commit is contained in:
11
Dockerfile
11
Dockerfile
@@ -0,0 +1,11 @@
|
||||
FROM golang:1.25-alpine AS builder
|
||||
WORKDIR /app/
|
||||
ADD go.mod go.sum ./
|
||||
RUN go mod download
|
||||
ADD . .
|
||||
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o unraid-exporter main.go
|
||||
|
||||
FROM golang:1.25-alpine
|
||||
WORKDIR /app/
|
||||
COPY --from=builder /app/unraid-exporter /app/unraid-exporter
|
||||
ENTRYPOINT ["/app/unraid-exporter"]
|
||||
|
||||
30
Makefile
30
Makefile
@@ -1,49 +1,33 @@
|
||||
.DEFAULT_GOAL := default
|
||||
|
||||
IMAGE ?= rg.fr-par.scw.cloud/averagemarcus-private/unraid-exporter:latest
|
||||
IMAGE ?= rg.fr-par.scw.cloud/averagemarcus/unraid-exporter:latest
|
||||
|
||||
.PHONY: test # Run all tests, linting and format checks
|
||||
test: lint check-format run-tests
|
||||
|
||||
.PHONY: lint # Perform lint checks against code
|
||||
lint:
|
||||
@echo "⚠️ 'lint' unimplemented"
|
||||
# GO Projects
|
||||
# @go vet && golint -set_exit_status ./...
|
||||
@go vet && golint -set_exit_status ./...
|
||||
|
||||
.PHONY: check-format # Checks code formatting and returns a non-zero exit code if formatting errors found
|
||||
check-format:
|
||||
@echo "⚠️ 'check-format' unimplemented"
|
||||
# GO Projects
|
||||
# @gofmt -e -l .
|
||||
@gofmt -e -l .
|
||||
|
||||
.PHONY: format # Performs automatic format fixes on all code
|
||||
format:
|
||||
@echo "⚠️ 'format' unimplemented"
|
||||
# GO Projects
|
||||
# @gofmt -s -w .
|
||||
@gofmt -s -w .
|
||||
|
||||
.PHONY: run-tests # Runs all tests
|
||||
run-tests:
|
||||
@echo "⚠️ 'run-tests' unimplemented"
|
||||
# GO Projects
|
||||
# @go test
|
||||
# Node Projects
|
||||
# @npm test
|
||||
@go test
|
||||
|
||||
.PHONY: fetch-deps # Fetch all project dependencies
|
||||
fetch-deps:
|
||||
@echo "⚠️ 'fetch-deps' unimplemented"
|
||||
# GO Projects
|
||||
# @go mod tidy
|
||||
# Node Projects
|
||||
# @npm install
|
||||
@go mod tidy
|
||||
|
||||
.PHONY: build # Build the project
|
||||
build: lint check-format fetch-deps
|
||||
@echo "⚠️ 'build' unimplemented"
|
||||
# GO Projects
|
||||
# @go build -o PROJECT_NAME main.go
|
||||
@go build -o unraid-exporter main.go
|
||||
|
||||
.PHONY: docker-build # Build the docker image
|
||||
docker-build:
|
||||
|
||||
45
collector.go
Normal file
45
collector.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type UnraidCollector struct {
|
||||
parityStatusMetric *prometheus.Desc
|
||||
parityProgressMetric *prometheus.Desc
|
||||
parityErrorCountMetric *prometheus.Desc
|
||||
|
||||
metrics []prometheus.Metric
|
||||
}
|
||||
|
||||
func newUnraidCollector() *UnraidCollector {
|
||||
return &UnraidCollector{
|
||||
parityStatusMetric: prometheus.NewDesc("unraid_parity_check_status",
|
||||
"Parity check status of the Unraid array",
|
||||
[]string{"status"},
|
||||
nil,
|
||||
),
|
||||
parityProgressMetric: prometheus.NewDesc("unraid_parity_check_progress",
|
||||
"Parity check progress percentage",
|
||||
nil, nil,
|
||||
),
|
||||
parityErrorCountMetric: prometheus.NewDesc("unraid_parity_check_error_count",
|
||||
"Parity check error count",
|
||||
nil, nil,
|
||||
),
|
||||
|
||||
metrics: []prometheus.Metric{},
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *UnraidCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- collector.parityStatusMetric
|
||||
ch <- collector.parityProgressMetric
|
||||
ch <- collector.parityErrorCountMetric
|
||||
}
|
||||
|
||||
func (collector *UnraidCollector) Collect(ch chan<- prometheus.Metric) {
|
||||
for _, m := range collector.metrics {
|
||||
ch <- m
|
||||
}
|
||||
}
|
||||
23
go.mod
Normal file
23
go.mod
Normal file
@@ -0,0 +1,23 @@
|
||||
module unraid-exporter
|
||||
|
||||
go 1.25.4
|
||||
|
||||
require (
|
||||
github.com/hasura/go-graphql-client v0.15.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coder/websocket v1.8.13 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
)
|
||||
52
go.sum
Normal file
52
go.sum
Normal file
@@ -0,0 +1,52 @@
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hasura/go-graphql-client v0.15.0 h1:C8gO+pilV5jyH7zuvQ0tJwxt/QSXRrhEJz35phPLk9Y=
|
||||
github.com/hasura/go-graphql-client v0.15.0/go.mod h1:jfSZtBER3or+88Q9vFhWHiFMPppfYILRyl+0zsgPIIw=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
129
main.go
Normal file
129
main.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
graphql "github.com/hasura/go-graphql-client"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
var (
|
||||
interval int
|
||||
timeout int
|
||||
port int
|
||||
endpoint string
|
||||
|
||||
graphqlClient *graphql.Client
|
||||
)
|
||||
|
||||
var statuses = []string{
|
||||
"NEVER_RUN",
|
||||
"RUNNING",
|
||||
"PAUSED",
|
||||
"COMPLETED",
|
||||
"CANCELLED",
|
||||
"FAILED",
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.IntVar(&interval, "interval", 30, "Duration, in seconds, between checks")
|
||||
flag.IntVar(&timeout, "timeout", 5000, "Timeout in ms when connecting to Unraid")
|
||||
flag.IntVar(&port, "port", 9091, "The port to listen on")
|
||||
flag.StringVar(&endpoint, "endpoint", "", "The Unraid GraphQL endpoint")
|
||||
flag.Parse()
|
||||
|
||||
envEndpoint := os.Getenv("UNRAID_ENDPOINT")
|
||||
if envEndpoint != "" {
|
||||
endpoint = envEndpoint
|
||||
}
|
||||
if endpoint == "" {
|
||||
panic("Endpoint must be provided")
|
||||
}
|
||||
|
||||
token := os.Getenv("UNRAID_TOKEN")
|
||||
if token == "" {
|
||||
panic("UNRAID_TOKEN env var must be provided")
|
||||
}
|
||||
|
||||
graphqlClient = graphql.NewClient(
|
||||
endpoint,
|
||||
&http.Client{
|
||||
Timeout: time.Millisecond * time.Duration(timeout),
|
||||
},
|
||||
).
|
||||
WithRequestModifier(func(r *http.Request) {
|
||||
r.Header.Set("content-type", "application/json")
|
||||
r.Header.Set("x-api-key", token)
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("Unraid-exporter")
|
||||
collector := newUnraidCollector()
|
||||
prometheus.MustRegister(collector)
|
||||
|
||||
go func() {
|
||||
if err := fetchUnraidStatus(collector); err != nil {
|
||||
fmt.Printf("failed to get status from Unraid: %v\n", err)
|
||||
}
|
||||
for range time.Tick(time.Second * time.Duration(interval)) {
|
||||
if err := fetchUnraidStatus(collector); err != nil {
|
||||
fmt.Printf("failed to get status from Unraid: %v\n", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
fmt.Printf("Starting server on %d\n", port)
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
|
||||
}
|
||||
|
||||
func fetchUnraidStatus(collector *UnraidCollector) error {
|
||||
var query struct {
|
||||
Array struct {
|
||||
ParityCheckStatus struct {
|
||||
Progress int64
|
||||
Status string
|
||||
Date string
|
||||
Duration int64
|
||||
Errors int64
|
||||
Speed string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := graphqlClient.Query(context.Background(), &query, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collector.metrics = []prometheus.Metric{
|
||||
prometheus.MustNewConstMetric(
|
||||
collector.parityProgressMetric, prometheus.CounterValue,
|
||||
float64(query.Array.ParityCheckStatus.Progress),
|
||||
),
|
||||
prometheus.MustNewConstMetric(
|
||||
collector.parityErrorCountMetric, prometheus.CounterValue,
|
||||
float64(query.Array.ParityCheckStatus.Errors),
|
||||
),
|
||||
}
|
||||
for _, status := range statuses {
|
||||
val := 0
|
||||
if status == query.Array.ParityCheckStatus.Status {
|
||||
val = 1
|
||||
}
|
||||
collector.metrics = append(collector.metrics, prometheus.MustNewConstMetric(
|
||||
collector.parityStatusMetric, prometheus.CounterValue,
|
||||
float64(val), status,
|
||||
))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user