From a8b90652654bda59cc728725fea99781cd9890c8 Mon Sep 17 00:00:00 2001 From: Ivaylo Papratilov Date: Thu, 7 Mar 2024 15:31:43 +0200 Subject: [PATCH 1/5] Initialize project --- .github/workflows/build.yaml | 92 ++++++++++++++ .github/workflows/golangci-lint.yaml | 18 +++ .gitignore | 3 + .golangci.yaml | 87 ++++++++++++++ Dockerfile | 4 + Makefile | 38 ++++++ cmd/main.go | 4 + go.mod | 56 +++++++++ go.sum | 171 +++++++++++++++++++++++++++ 9 files changed, 473 insertions(+) create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/golangci-lint.yaml create mode 100644 .gitignore create mode 100644 .golangci.yaml create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 cmd/main.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..84aa007 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,92 @@ +name: Build + +on: + push: + branches: + - main + release: + types: + - published + pull_request: + branches: + - main + +jobs: + build: + name: Build + runs-on: ubuntu-20.04 + steps: + + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: 1.22 + + + - name: Get release tag + if: github.event_name == 'release' + run: echo "RELEASE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Build Go binary amd64 + run: go build -ldflags "-s -w -X main.GitCommit=$GITHUB_SHA -X main.GitRef=$GITHUB_REF -X main.Version=${RELEASE_TAG:-commit-$GITHUB_SHA}" -o bin/gpu-metrics-exporter-amd64 ./cmd/main.go + env: + GOOS: linux + GOARCH: amd64 + CGO_ENABLED: 0 + + - name: Build Go binary arm64 + run: go build -ldflags "-s -w -X main.GitCommit=$GITHUB_SHA -X main.GitRef=$GITHUB_REF -X main.Version=${RELEASE_TAG:-commit-$GITHUB_SHA}" -o bin/gpu-metrics-exporter-arm64 ./cmd/main.go + env: + GOOS: linux + GOARCH: arm64 + CGO_ENABLED: 0 + + - name: Test + run: go test -race ./... + + # - name: Set up QEMU + # uses: docker/setup-qemu-action@v3 + + # - name: Set up Docker Buildx + # uses: docker/setup-buildx-action@v3 + + # - name: Login to GitHub Container Registry + # uses: docker/login-action@v2 + # with: + # registry: ghcr.io + # username: ${{ github.actor }} + # password: ${{ secrets.GITHUB_TOKEN }} + + # - name: Build and push PR + # if: ${{ github.event_name == 'pull_request' }} + # uses: docker/build-push-action@v5 + # with: + # context: . + # platforms: linux/arm64,linux/amd64 + # file: ./Dockerfile + # push: true + # tags: ghcr.io/castai/gpu-metrics-exporter/gpu-metrics-exporter:${{ github.sha }} + + # - name: Build and push main + # if: ${{ github.event_name != 'pull_request' && github.event_name != 'release' }} + # uses: docker/build-push-action@v5 + # with: + # context: . + # platforms: linux/arm64,linux/amd64 + # file: ./Dockerfile + # push: true + # tags: ghcr.io/castai/egressd/egressd:${{ github.sha }} + + # - name: Build and push release (egressd collector) + # uses: docker/build-push-action@v5 + # with: + # context: . + # push: true + # platforms: linux/arm64,linux/amd64 + # file: ./Dockerfile + # tags: | + # ghcr.io/castai/gpu-metrics-exporter/gpu-metrics-exporter:${{ env.RELEASE_TAG }} + # ghcr.io/castai/gpu-metrics-exporter/gpu-metrics-exporter:latest \ No newline at end of file diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml new file mode 100644 index 0000000..5ebc721 --- /dev/null +++ b/.github/workflows/golangci-lint.yaml @@ -0,0 +1,18 @@ +name: golangci-lint +on: + pull_request: +permissions: + contents: read +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.22.0 + - name: golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + args: --timeout=5m \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab2b863 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin +coverage.txt +.vscode diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..86af2d6 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,87 @@ +linters-settings: + golint: + min-confidence: 0 + gomnd: + settings: + mnd: + # don't include the "operation" and "assign" + checks: [argument,case,condition,return] + govet: + # shadow is marked as experimental feature, skip it for now. + check-shadowing: false + settings: + printf: + funcs: + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf + lll: + line-length: 200 + maligned: + suggest-new: true + misspell: + locale: US + revive: + rules: + - name: redefines-builtin-id + disabled: true + - name: nested-structs + disabled: true + gci: + sections: + - standard + - default + - prefix(github.com/castai) +linters: + disable-all: true + enable: + - dogsled + - errcheck + - gofmt + - revive + - goprintffuncname + - gosimple + - gosec + - govet + - ineffassign + - lll + - misspell + - nakedret + - exportloopref + - staticcheck + - stylecheck + - typecheck + - unconvert + - unused + - whitespace + - errname + - dupword + - gci + - containedctx + - durationcheck + - errorlint +issues: + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + - path: _test\.go + linters: + - gomnd + - bodyclose + - gosec + - linters: + - gosec + # Ignored gosec G107 rule because of many false positives. It states that `http.Get(url)` must not contain a variable. + text: G107 + - linters: + - unparam + # ignoring error where function always receives same value - mostly in tests + text: "always receives" + - linters: + - revive + # tolerate code where errors are returned as if-return + text: "redundant if ...; err != nil check, just return error instead." +run: + skip-dirs: + - gen + - e2e diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3bebacf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM gcr.io/distroless/static-debian11:nonroot +ARG TARGETARCH +COPY bin/gpu-metrics-exporter-$TARGETARCH /usr/local/bin/gpu-metrics-exporter +CMD ["gpu-metrics-exporter"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2430b35 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +.PHONY: lint +lint: check-lint-dependencies + golangci-lint run ./... + +.PHONY: fix-lint-lint +fix-lint: check-lint-dependencies + golangci-lint run ./... --fix + +.PHONY: build +build: + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w" -o bin/gpu-metrics-exporter-amd64 ./cmd/main.go + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags "-s -w" -o bin/gpu-metrics-exporter-arm64 ./cmd/main.go + docker buildx build --push --platform=linux/amd64,linux/arm64 -t $(TAG) . + +.PHONY: generate +generate: + go generate ./... + +SHELL := /bin/bash +.PHONY: run +run: + go run ./cmd/main.go + +.PHONY: test +test: + go test ./... -race -coverprofile=coverage.txt -covermode=atomic + +.PHONY: gen-proto +gen-proto: check-proto-dependencies + protoc pb/metrics.proto --go_out=paths=source_relative:. + +.PHONY: check-lint-dependencies +check-lint-dependencies: + @which golangci-lint > /dev/null || (echo "golangci-lint not found, please install it" && exit 1) + +.PHONY: check-proto-dependencies +check-proto-dependencies: + @which protoc > /dev/null || (echo "protoc not found, please install it" && exit 1) \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..da29a2c --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,4 @@ +package main + +func main() { +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..adbc4e6 --- /dev/null +++ b/go.mod @@ -0,0 +1,56 @@ +module github.com/castai/gpu-metrics-exporter + +go 1.22.0 + +require ( + github.com/kelseyhightower/envconfig v1.4.0 + github.com/prometheus/client_model v0.6.0 + github.com/prometheus/common v0.49.0 + github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.8.4 + golang.org/x/sync v0.6.0 + google.golang.org/protobuf v1.32.0 + k8s.io/api v0.29.2 + k8s.io/apimachinery v0.29.2 + k8s.io/client-go v0.29.2 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/oauth2 v0.17.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7931ea2 --- /dev/null +++ b/go.sum @@ -0,0 +1,171 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +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/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI= +github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE= +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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= +k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= +k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= +k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= +k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From aedfe934d52c569ddb8a6dac6fa9cc0d0ebf39b7 Mon Sep 17 00:00:00 2001 From: Ivaylo Papratilov Date: Thu, 7 Mar 2024 15:33:42 +0200 Subject: [PATCH 2/5] Add basic exporter application --- .mockery.yaml | 9 + cmd/main.go | 123 ++++++++++ gen_mockery.go | 2 + go.mod | 2 + go.sum | 2 + internal/config/config.go | 25 ++ internal/exporter/exporter.go | 117 +++++++++ internal/exporter/exporter_test.go | 92 +++++++ internal/exporter/mapper.go | 66 +++++ internal/exporter/mapper_test.go | 117 +++++++++ internal/exporter/scraper.go | 119 +++++++++ internal/exporter/scraper_test.go | 166 +++++++++++++ internal/exporter/types.go | 41 ++++ internal/server/server.go | 26 ++ mock/exporter/mock_Exporter.go | 191 +++++++++++++++ mock/exporter/mock_HttpClient.go | 94 +++++++ mock/exporter/mock_MetricMapper.go | 88 +++++++ mock/exporter/mock_Scraper.go | 96 ++++++++ pb/metrics.pb.go | 380 +++++++++++++++++++++++++++++ pb/metrics.proto | 25 ++ 20 files changed, 1781 insertions(+) create mode 100644 .mockery.yaml create mode 100644 gen_mockery.go create mode 100644 internal/config/config.go create mode 100644 internal/exporter/exporter.go create mode 100644 internal/exporter/exporter_test.go create mode 100644 internal/exporter/mapper.go create mode 100644 internal/exporter/mapper_test.go create mode 100644 internal/exporter/scraper.go create mode 100644 internal/exporter/scraper_test.go create mode 100644 internal/exporter/types.go create mode 100644 internal/server/server.go create mode 100644 mock/exporter/mock_Exporter.go create mode 100644 mock/exporter/mock_HttpClient.go create mode 100644 mock/exporter/mock_MetricMapper.go create mode 100644 mock/exporter/mock_Scraper.go create mode 100644 pb/metrics.pb.go create mode 100644 pb/metrics.proto diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 0000000..7a9f7d3 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,9 @@ +with-expecter: true +dir: 'mock/{{replace .InterfaceDirRelative "internal" "" 1}}' +packages: + "github.com/castai/gpu-metrics-exporter/internal/exporter": + interfaces: + Exporter: + Scraper: + MetricMapper: + HttpClient: \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index da29a2c..dd3cb31 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,4 +1,127 @@ package main +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/flowcontrol" + + "github.com/castai/gpu-metrics-exporter/internal/config" + "github.com/castai/gpu-metrics-exporter/internal/exporter" + "github.com/castai/gpu-metrics-exporter/internal/server" +) + func main() { + log := logrus.New() + + cfg, err := config.GetFromEnvironment() + if err != nil { + log.Fatal(err) + } + + logLevel, err := logrus.ParseLevel(cfg.LogLevel) + if err != nil { + log.Fatal(err) + } + log.SetLevel(logLevel) + + if err := run(cfg, log); err != nil && !errors.Is(err, context.Canceled) { + log.Fatal(err) + } +} + +func run(cfg *config.Config, log logrus.FieldLogger) error { + mux := server.NewServerMux(log) + + srv := &http.Server{ + Addr: fmt.Sprintf(":%d", cfg.HTTPListenPort), + Handler: mux, + ReadHeaderTimeout: 3 * time.Second, + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { + stopper := make(chan os.Signal, 1) + signal.Notify(stopper, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) + <-stopper + + ctx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer shutdownCancel() + if err := srv.Shutdown(ctx); err != nil { + log.Errorf("http server shutdown: %v", err) + } + + cancel() + }() + + clientset, err := newKubernetesClientset(cfg) + if err != nil { + log.Fatal(err) + } + + labelSelector, err := selectorFromMap(cfg.DCGMLabels) + if err != nil { + log.Fatal(err) + } + + scraper := exporter.NewScraper(&http.Client{}, log) + mapper := exporter.NewMapper() + ex := exporter.NewExporter(exporter.Config{ + ExportInterval: cfg.ExportInterval, + Selector: labelSelector.String(), + DCGMExporterPort: 9400, + DCGMExporterPath: "/metrics", + Enabled: true, + }, clientset, log, scraper, mapper) + + go func() { + if err := ex.Start(ctx); err != nil && !errors.Is(err, context.Canceled) { + log.Errorf("exporter stopped with error %v", err) + } + }() + + return srv.ListenAndServe() +} + +func newKubernetesClientset(cfg *config.Config) (*kubernetes.Clientset, error) { + config, err := clientcmd.BuildConfigFromFlags("", cfg.KubeConfigPath) + if err != nil { + return nil, err + } + config.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(float32(10), 25) + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + return clientset, nil +} + +func selectorFromMap(labelMap map[string]string) (labels.Selector, error) { + selector := labels.NewSelector() + var requirements labels.Requirements + + for label, value := range labelMap { + req, err := labels.NewRequirement(label, selection.Equals, []string{value}) + if err != nil { + return nil, err + } + requirements = append(requirements, *req) + } + + return selector.Add(requirements...), nil } diff --git a/gen_mockery.go b/gen_mockery.go new file mode 100644 index 0000000..5c48479 --- /dev/null +++ b/gen_mockery.go @@ -0,0 +1,2 @@ +//go:generate go run github.com/vektra/mockery/v2@v2.42.0 --all +package mockery diff --git a/go.mod b/go.mod index adbc4e6..c6ae479 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -36,6 +37,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect diff --git a/go.sum b/go.sum index 7931ea2..0e668d9 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..e810bd2 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,25 @@ +package config + +import ( + "time" + + "github.com/kelseyhightower/envconfig" +) + +type Config struct { + HTTPListenPort int `envconfig:"HTTP_LISTEN_PORT" default:"6061"` + LogLevel string `envconfig:"LOG_LEVEL" default:"info"` + KubeConfigPath string `envconfig:"KUBE_CONFIG_PATH"` + DCGMLabels map[string]string `envconfig:"DCGM_LABELS" default:"app.kubernetes.io/component:dcgm-exporter"` + ExportInterval time.Duration `envconfig:"EXPORT_INTERVAL" default:"15s"` + CastAPI string `envconfig:"CAST_API" default:"https://api.cast.ai"` + APIToken string `envconfig:"API_TOKEN"` +} + +func GetFromEnvironment() (*Config, error) { + cfg := &Config{} + if err := envconfig.Process("", cfg); err != nil { + return nil, err + } + return cfg, nil +} diff --git a/internal/exporter/exporter.go b/internal/exporter/exporter.go new file mode 100644 index 0000000..dc79df4 --- /dev/null +++ b/internal/exporter/exporter.go @@ -0,0 +1,117 @@ +package exporter + +import ( + "context" + "fmt" + "sync/atomic" + "time" + + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + cleanupInterval = 3 * time.Minute +) + +type Exporter interface { + Start(ctx context.Context) error + Enable() + Disable() + Enabled() bool +} + +type Config struct { + ExportInterval time.Duration + DCGMExporterPort int + DCGMExporterPath string + Selector string + Enabled bool +} + +type exporter struct { + cfg Config + kube kubernetes.Interface + log logrus.FieldLogger + scraper Scraper + mapper MetricMapper + enabled *atomic.Bool +} + +func NewExporter(cfg Config, kube kubernetes.Interface, log logrus.FieldLogger, scraper Scraper, mapper MetricMapper) Exporter { + enabled := atomic.Bool{} + enabled.Store(cfg.Enabled) + + return &exporter{ + cfg: cfg, + kube: kube, + log: log, + scraper: scraper, + mapper: mapper, + enabled: &enabled, + } +} + +func (e *exporter) Start(ctx context.Context) error { + exportTicker := time.NewTicker(e.cfg.ExportInterval) + defer exportTicker.Stop() + + cleanupTicker := time.NewTicker(cleanupInterval) + defer cleanupTicker.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-exportTicker.C: + if !e.enabled.Load() { + continue + } + if err := e.collect(ctx); err != nil { + e.log.Errorf("collect error: %v", err) + } + case <-cleanupTicker.C: + // TODO: call cleanup procedure + } + } +} + +func (e *exporter) Enable() { + e.enabled.Store(true) +} + +func (e *exporter) Disable() { + e.enabled.Store(false) +} + +func (e *exporter) Enabled() bool { + return e.enabled.Load() +} + +func (e *exporter) collect(ctx context.Context) error { + // TODO: consider using an informer and keeping a list of pods which match the selector + // at the moment seems like an overkill + dcgmExporterList, err := e.kube.CoreV1().Pods("").List(ctx, metav1.ListOptions{ + LabelSelector: e.cfg.Selector, + FieldSelector: "status.phase=Running", + }) + if err != nil { + return fmt.Errorf("error getting DCGM exporter pods %w", err) + } + + urls := make([]string, len(dcgmExporterList.Items)) + for i := range dcgmExporterList.Items { + dcgmExporter := dcgmExporterList.Items[i] + urls[i] = fmt.Sprintf("http://%s:%d%s", dcgmExporter.Status.PodIP, e.cfg.DCGMExporterPort, e.cfg.DCGMExporterPath) + } + + metricFamilies, err := e.scraper.Scrape(ctx, urls) + if err != nil { + return fmt.Errorf("couldn't scrape DCGM exporters %w", err) + } + metrics := e.mapper.Map(metricFamilies, time.Now()) + _ = metrics + + return nil +} diff --git a/internal/exporter/exporter_test.go b/internal/exporter/exporter_test.go new file mode 100644 index 0000000..807c1ed --- /dev/null +++ b/internal/exporter/exporter_test.go @@ -0,0 +1,92 @@ +package exporter_test + +import ( + "context" + "errors" + "testing" + "time" + + dto "github.com/prometheus/client_model/go" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + + "github.com/castai/gpu-metrics-exporter/internal/exporter" + mocks "github.com/castai/gpu-metrics-exporter/mock/exporter" +) + +func TestExporter_Running(t *testing.T) { + log := logrus.New() + ctx := context.Background() + + kubeClient := fake.NewSimpleClientset(&corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dcgm-exporter", + Namespace: "default", + Labels: map[string]string{"app": "dcgm-exporter"}, + }, + Status: corev1.PodStatus{ + PodIP: "192.168.1.1", + Phase: corev1.PodRunning, + }, + }) + + config := exporter.Config{ + ExportInterval: 2 * time.Second, + DCGMExporterPort: 9400, + DCGMExporterPath: "/metrics", + Selector: "app=dcgm-exporter", + Enabled: true, + } + + scraper := mocks.NewMockScraper(t) + mapper := mocks.NewMockMetricMapper(t) + + ex := exporter.NewExporter(config, kubeClient, log, scraper, mapper) + ex.Enable() + + metricFamilies := []exporter.MetricFamiliyMap{ + { + "test_gauge": { + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{ + { + Label: []*dto.LabelPair{ + newLabelPair("label1", "value1"), + }, + Gauge: newGauge(1.0), + }, + }, + }, + exporter.MetricGraphicsEngineActive: { + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{ + { + Label: []*dto.LabelPair{ + newLabelPair("label1", "value1"), + }, + Gauge: newGauge(1.0), + }, + }, + }, + }, + } + + scraper.EXPECT().Scrape(ctx, []string{"http://192.168.1.1:9400/metrics"}).Times(1).Return(metricFamilies, nil) + mapper.EXPECT().Map(metricFamilies, mock.Anything).Times(1).Return(nil, nil) + + go func() { + err := ex.Start(ctx) + if err != nil && !errors.Is(err, context.Canceled) { + t.Errorf("unexpected error: %v", err) + } + }() + + time.Sleep(2400 * time.Millisecond) + + r := require.New(t) + r.True(ex.Enabled()) +} diff --git a/internal/exporter/mapper.go b/internal/exporter/mapper.go new file mode 100644 index 0000000..0239a56 --- /dev/null +++ b/internal/exporter/mapper.go @@ -0,0 +1,66 @@ +package exporter + +import ( + "time" + + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/castai/gpu-metrics-exporter/pb" +) + +type MetricMapper interface { + Map(metrics []MetricFamiliyMap, ts time.Time) *pb.MetricsBatch +} + +type metricMapper struct{} + +func NewMapper() MetricMapper { + return &metricMapper{} +} + +func (p metricMapper) Map(metricFamilyMaps []MetricFamiliyMap, ts time.Time) *pb.MetricsBatch { + metrics := &pb.MetricsBatch{} + metricsMap := make(map[string]*pb.Metric) + for _, familyMap := range metricFamilyMaps { + for name, family := range familyMap { + if _, found := EnabledMetrics[name]; !found { + continue + } + + metric, found := metricsMap[name] + if !found { + metric = &pb.Metric{ + Name: name, + } + metricsMap[name] = metric + metrics.Metrics = append(metrics.Metrics, metric) + } + t := family.Type.String() + for _, m := range family.Metric { + labels := []*pb.Metric_Label{} + for _, l := range m.Label { + labels = append(labels, &pb.Metric_Label{ + Name: *l.Name, + Value: *l.Value, + }) + } + switch t { + case "COUNTER": + metric.Measurements = append(metric.Measurements, &pb.Metric_Measurement{ + Value: *m.GetCounter().Value, + Ts: timestamppb.New(ts), + Labels: labels, + }) + case "GAUGE": + metric.Measurements = append(metric.Measurements, &pb.Metric_Measurement{ + Value: *m.GetGauge().Value, + Ts: timestamppb.New(ts), + Labels: labels, + }) + } + } + } + } + + return metrics +} diff --git a/internal/exporter/mapper_test.go b/internal/exporter/mapper_test.go new file mode 100644 index 0000000..5a97d18 --- /dev/null +++ b/internal/exporter/mapper_test.go @@ -0,0 +1,117 @@ +package exporter_test + +import ( + "testing" + "time" + + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/castai/gpu-metrics-exporter/internal/exporter" + "github.com/castai/gpu-metrics-exporter/pb" +) + +func newGauge(value float64) *dto.Gauge { + return &dto.Gauge{ + Value: &value, + } +} + +func newLabelPair(name, value string) *dto.LabelPair { + return &dto.LabelPair{ + Name: &name, + Value: &value, + } +} + +func TestMetricMapper_Map(t *testing.T) { + mapper := exporter.NewMapper() + + t.Run("empty input yields empty MetricsBatch", func(t *testing.T) { + ts := time.Now() + metricFamilyMaps := []exporter.MetricFamiliyMap{} + + got := mapper.Map(metricFamilyMaps, ts) + expected := &pb.MetricsBatch{} + + r := require.New(t) + r.Equal(expected, got) + }) + + t.Run("metric familiy which is not enabled is skipped", func(t *testing.T) { + ts := time.Now() + metricFamilyMaps := []exporter.MetricFamiliyMap{ + { + "test_gauge": { + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{ + { + Label: []*dto.LabelPair{ + newLabelPair("label1", "value1"), + }, + Gauge: newGauge(1.0), + }, + }, + }, + }, + } + + got := mapper.Map(metricFamilyMaps, ts) + expected := &pb.MetricsBatch{} + + r := require.New(t) + r.Equal(expected, got) + }) + + t.Run("enabled metric family is included", func(t *testing.T) { + ts := time.Now() + metricFamilyMaps := []exporter.MetricFamiliyMap{ + { + "test_gauge": { + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{ + { + Label: []*dto.LabelPair{ + newLabelPair("label1", "value1"), + }, + Gauge: newGauge(1.0), + }, + }, + }, + exporter.MetricGraphicsEngineActive: { + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{ + { + Label: []*dto.LabelPair{ + newLabelPair("label1", "value1"), + }, + Gauge: newGauge(1.0), + }, + }, + }, + }, + } + + got := mapper.Map(metricFamilyMaps, ts) + expected := &pb.MetricsBatch{ + Metrics: []*pb.Metric{ + { + Name: exporter.MetricGraphicsEngineActive, + Measurements: []*pb.Metric_Measurement{ + { + Value: 1.0, + Ts: timestamppb.New(ts), + Labels: []*pb.Metric_Label{ + {Name: "label1", Value: "value1"}, + }, + }, + }, + }, + }, + } + + r := require.New(t) + r.Equal(expected, got) + }) +} diff --git a/internal/exporter/scraper.go b/internal/exporter/scraper.go new file mode 100644 index 0000000..be7a5d2 --- /dev/null +++ b/internal/exporter/scraper.go @@ -0,0 +1,119 @@ +package exporter + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + dto "github.com/prometheus/client_model/go" + "github.com/prometheus/common/expfmt" + "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" +) + +const ( + maxConcurrentScrapes = 15 +) + +type MetricFamiliyMap map[string]*dto.MetricFamily + +type Scraper interface { + Scrape(ctx context.Context, urls []string) ([]MetricFamiliyMap, error) +} + +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +type result struct { + metricFamilyMap MetricFamiliyMap + err error + ts time.Time +} + +type scraper struct { + httpClient HTTPClient + parser expfmt.TextParser + log logrus.FieldLogger +} + +func NewScraper(httpClient HTTPClient, log logrus.FieldLogger) Scraper { + return &scraper{ + httpClient: httpClient, + log: log, + } +} + +func (s scraper) Scrape(ctx context.Context, urls []string) ([]MetricFamiliyMap, error) { + var g errgroup.Group + g.SetLimit(maxConcurrentScrapes) + + resultsChan := make(chan result, maxConcurrentScrapes) + + now := time.Now().UTC() + + for i := range urls { + url := urls[i] + g.Go(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + metrics, err := s.scrapeURL(ctx, url) + if err != nil { + err = fmt.Errorf("error while fetching metrics from '%s' %w", url, err) + } + resultsChan <- result{metricFamilyMap: metrics, err: err, ts: now} + } + return nil + }) + } + + go func() { + if err := g.Wait(); err != nil && !errors.Is(err, context.Canceled) { + s.log.Errorf("error while scraping metrics %v", err) + } + close(resultsChan) + }() + + metrics := make([]MetricFamiliyMap, 0, len(urls)) + for result := range resultsChan { + if result.err != nil { + s.log.Error(result.err) + continue + } + metrics = append(metrics, result.metricFamilyMap) + } + + return metrics, nil +} + +func (s scraper) scrapeURL(ctx context.Context, url string) (map[string]*dto.MetricFamily, error) { + ctxWithTimeout, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + req, err := http.NewRequestWithContext(ctxWithTimeout, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("cannot create request %w", err) + } + + resp, err := s.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("error while making http request %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("request failed with status code: %d", resp.StatusCode) + } + + defer resp.Body.Close() + + metrics, err := s.parser.TextToMetricFamilies(resp.Body) + if err != nil { + return nil, fmt.Errorf("cannot parse metrics %w", err) + } + + return metrics, nil +} diff --git a/internal/exporter/scraper_test.go b/internal/exporter/scraper_test.go new file mode 100644 index 0000000..8fb22e6 --- /dev/null +++ b/internal/exporter/scraper_test.go @@ -0,0 +1,166 @@ +package exporter_test + +import ( + "context" + "errors" + "io" + "net/http" + "strings" + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/castai/gpu-metrics-exporter/internal/exporter" + mocks "github.com/castai/gpu-metrics-exporter/mock/exporter" +) + +var ( + metricsString = ` + # HELP DCGM_FI_DEV_GPU_TEMP Current temperature readings for the device in degrees C. + # TYPE DCGM_FI_DEV_GPU_TEMP gauge + DCGM_FI_DEV_GPU_TEMP{gpu="0",UUID="GPU-93461651-6be6-8fb7-a69a-c9eedc6984db",device="nvidia0",modelName="Tesla T4",Hostname="gke-gpu-default-pool",container="",namespace="",pod=""} 40 + ` +) + +func TestScraper_Scrape(t *testing.T) { + log := logrus.New() + + t.Run("scrapes metrics without error", func(t *testing.T) { + httpClient := mocks.NewMockHttpClient(t) + scraper := exporter.NewScraper(httpClient, log) + + response1 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(metricsString)), + } + + response2 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(metricsString)), + } + + response3 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(metricsString)), + } + + httpClient.EXPECT().Do(mock.MatchedBy(func(req *http.Request) bool { + return req.URL.Host == "localhost:9400" && req.URL.Path == "/metrics" + })).Times(1).Return(response1, nil) + + httpClient.EXPECT().Do(mock.MatchedBy(func(req *http.Request) bool { + return req.URL.Host == "localhost:9410" && req.URL.Path == "/metrics" + })).Times(1).Return(response2, nil) + + httpClient.EXPECT().Do(mock.MatchedBy(func(req *http.Request) bool { + return req.URL.Host == "localhost:9420" && req.URL.Path == "/metrics" + })).Times(1).Return(response3, nil) + + metricsFamily, err := scraper.Scrape( + context.Background(), + []string{ + "http://localhost:9400/metrics", + "http://localhost:9410/metrics", + "http://localhost:9420/metrics", + }) + + r := require.New(t) + r.NoError(err) + r.NotNil(metricsFamily) + r.Len(metricsFamily, 3) + r.NotEmpty(metricsFamily[0]) + r.NotEmpty(metricsFamily[1]) + r.NotEmpty(metricsFamily[2]) + }) + + t.Run("partially scrapes metrics when some exporter returns non-200 code", func(t *testing.T) { + httpClient := mocks.NewMockHttpClient(t) + scraper := exporter.NewScraper(httpClient, log) + + response := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(metricsString)), + } + + response1 := &http.Response{ + StatusCode: http.StatusInternalServerError, + Body: io.NopCloser(strings.NewReader("")), + } + + response2 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(metricsString)), + } + + httpClient.EXPECT().Do(mock.MatchedBy(func(req *http.Request) bool { + return req.URL.Host == "localhost:9400" && req.URL.Path == "/metrics" + })).Times(1).Return(response, nil) + + httpClient.EXPECT().Do(mock.MatchedBy(func(req *http.Request) bool { + return req.URL.Host == "localhost:9410" && req.URL.Path == "/metrics" + })).Times(1).Return(response1, nil) + + httpClient.EXPECT().Do(mock.MatchedBy(func(req *http.Request) bool { + return req.URL.Host == "localhost:9420" && req.URL.Path == "/metrics" + })).Times(1).Return(response2, nil) + + metricsFamily, err := scraper.Scrape( + context.Background(), + []string{ + "http://localhost:9400/metrics", + "http://localhost:9410/metrics", + "http://localhost:9420/metrics", + }) + + r := require.New(t) + r.NoError(err) + r.NotNil(metricsFamily) + r.Len(metricsFamily, 2) + r.NotEmpty(metricsFamily[0]) + r.NotEmpty(metricsFamily[1]) + }) + + t.Run("partially scrapes metrics when some exporter cannot be scraped", func(t *testing.T) { + httpClient := mocks.NewMockHttpClient(t) + scraper := exporter.NewScraper(httpClient, log) + + response := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(metricsString)), + } + + response1 := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(metricsString)), + } + + httpClient.EXPECT().Do(mock.MatchedBy(func(req *http.Request) bool { + return req.URL.Host == "localhost:9400" && req.URL.Path == "/metrics" + })).Times(1).Return(response, nil) + + httpClient.EXPECT().Do(mock.MatchedBy(func(req *http.Request) bool { + return req.URL.Host == "localhost:9410" && req.URL.Path == "/metrics" + })).Times(1).Return(nil, errors.New("network error")) + + httpClient.EXPECT().Do(mock.MatchedBy(func(req *http.Request) bool { + return req.URL.Host == "localhost:9420" && req.URL.Path == "/metrics" + })).Times(1).Return(response1, nil) + + metricsFamily, err := scraper.Scrape( + context.Background(), + []string{ + "http://localhost:9400/metrics", + "http://localhost:9410/metrics", + "http://localhost:9420/metrics", + }) + + r := require.New(t) + r.NoError(err) + r.NotNil(metricsFamily) + r.Len(metricsFamily, 2) + r.NotEmpty(metricsFamily[0]) + r.NotEmpty(metricsFamily[1]) + }) +} diff --git a/internal/exporter/types.go b/internal/exporter/types.go new file mode 100644 index 0000000..9648c9c --- /dev/null +++ b/internal/exporter/types.go @@ -0,0 +1,41 @@ +package exporter + +type MetricName = string + +const ( + MetricStreamingMultiProcessorActive = MetricName("DCGM_FI_PROF_SM_ACTIVE") + MetricStreamingMultiProcessorOccupancy = MetricName("DCGM_FI_PROF_SM_OCCUPANCY") + MetricStreamingMultiProcessorTensorActive = MetricName("DCGM_FI_PROF_PIPE_TENSOR_ACTIVE") + MetricDRAMActive = MetricName("DCGM_FI_PROF_DRAM_ACTIVE") + MetricPCIeTXBytes = MetricName("DCGM_FI_PROF_PCIE_TX_BYTES") + MetricPCIeRXBytes = MetricName("DCGM_FI_PROF_PCIE_RX_BYTES") + MetricGraphicsEngineActive = MetricName("DCGM_FI_PROF_GR_ENGINE_ACTIVE") + MetricFrameBufferTotal = MetricName("DCGM_FI_DEV_FB_TOTAL") + MetricFrameBufferFree = MetricName("DCGM_FI_DEV_FB_FREE") + MetricFrameBufferUsed = MetricName("DCGM_FI_DEV_FB_USED") + MetricPCIeLinkGen = MetricName("DCGM_FI_DEV_PCIE_LINK_GEN") + MetricPCIeLinkWidth = MetricName("DCGM_FI_DEV_PCIE_LINK_WIDTH") + MetricGPUTemperature = MetricName("DCGM_FI_DEV_GPU_TEMP") + MetricMemoryTemperature = MetricName("DCGM_FI_DEV_MEMORY_TEMP") + MetricPowerUsage = MetricName("DCGM_FI_DEV_POWER_USAGE") +) + +var ( + EnabledMetrics = map[MetricName]struct{}{ + MetricStreamingMultiProcessorActive: {}, + MetricStreamingMultiProcessorOccupancy: {}, + MetricStreamingMultiProcessorTensorActive: {}, + MetricDRAMActive: {}, + MetricPCIeTXBytes: {}, + MetricPCIeRXBytes: {}, + MetricGraphicsEngineActive: {}, + MetricFrameBufferTotal: {}, + MetricFrameBufferFree: {}, + MetricFrameBufferUsed: {}, + MetricPCIeLinkGen: {}, + MetricPCIeLinkWidth: {}, + MetricGPUTemperature: {}, + MetricMemoryTemperature: {}, + MetricPowerUsage: {}, + } +) diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..65951e4 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,26 @@ +package server + +import ( + "net/http" + "net/http/pprof" + + "github.com/sirupsen/logrus" +) + +func healthHandler(w http.ResponseWriter, req *http.Request) { + _, _ = w.Write([]byte("Ok")) +} + +func NewServerMux(log logrus.FieldLogger) *http.ServeMux { + mux := http.NewServeMux() + + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + mux.HandleFunc("/healthz", healthHandler) + + return mux +} diff --git a/mock/exporter/mock_Exporter.go b/mock/exporter/mock_Exporter.go new file mode 100644 index 0000000..6cbd27a --- /dev/null +++ b/mock/exporter/mock_Exporter.go @@ -0,0 +1,191 @@ +// Code generated by mockery v2.42.0. DO NOT EDIT. + +package exporter + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockExporter is an autogenerated mock type for the Exporter type +type MockExporter struct { + mock.Mock +} + +type MockExporter_Expecter struct { + mock *mock.Mock +} + +func (_m *MockExporter) EXPECT() *MockExporter_Expecter { + return &MockExporter_Expecter{mock: &_m.Mock} +} + +// Disable provides a mock function with given fields: +func (_m *MockExporter) Disable() { + _m.Called() +} + +// MockExporter_Disable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Disable' +type MockExporter_Disable_Call struct { + *mock.Call +} + +// Disable is a helper method to define mock.On call +func (_e *MockExporter_Expecter) Disable() *MockExporter_Disable_Call { + return &MockExporter_Disable_Call{Call: _e.mock.On("Disable")} +} + +func (_c *MockExporter_Disable_Call) Run(run func()) *MockExporter_Disable_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockExporter_Disable_Call) Return() *MockExporter_Disable_Call { + _c.Call.Return() + return _c +} + +func (_c *MockExporter_Disable_Call) RunAndReturn(run func()) *MockExporter_Disable_Call { + _c.Call.Return(run) + return _c +} + +// Enable provides a mock function with given fields: +func (_m *MockExporter) Enable() { + _m.Called() +} + +// MockExporter_Enable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Enable' +type MockExporter_Enable_Call struct { + *mock.Call +} + +// Enable is a helper method to define mock.On call +func (_e *MockExporter_Expecter) Enable() *MockExporter_Enable_Call { + return &MockExporter_Enable_Call{Call: _e.mock.On("Enable")} +} + +func (_c *MockExporter_Enable_Call) Run(run func()) *MockExporter_Enable_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockExporter_Enable_Call) Return() *MockExporter_Enable_Call { + _c.Call.Return() + return _c +} + +func (_c *MockExporter_Enable_Call) RunAndReturn(run func()) *MockExporter_Enable_Call { + _c.Call.Return(run) + return _c +} + +// Enabled provides a mock function with given fields: +func (_m *MockExporter) Enabled() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Enabled") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// MockExporter_Enabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Enabled' +type MockExporter_Enabled_Call struct { + *mock.Call +} + +// Enabled is a helper method to define mock.On call +func (_e *MockExporter_Expecter) Enabled() *MockExporter_Enabled_Call { + return &MockExporter_Enabled_Call{Call: _e.mock.On("Enabled")} +} + +func (_c *MockExporter_Enabled_Call) Run(run func()) *MockExporter_Enabled_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockExporter_Enabled_Call) Return(_a0 bool) *MockExporter_Enabled_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockExporter_Enabled_Call) RunAndReturn(run func() bool) *MockExporter_Enabled_Call { + _c.Call.Return(run) + return _c +} + +// Start provides a mock function with given fields: ctx +func (_m *MockExporter) Start(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Start") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockExporter_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type MockExporter_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockExporter_Expecter) Start(ctx interface{}) *MockExporter_Start_Call { + return &MockExporter_Start_Call{Call: _e.mock.On("Start", ctx)} +} + +func (_c *MockExporter_Start_Call) Run(run func(ctx context.Context)) *MockExporter_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockExporter_Start_Call) Return(_a0 error) *MockExporter_Start_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockExporter_Start_Call) RunAndReturn(run func(context.Context) error) *MockExporter_Start_Call { + _c.Call.Return(run) + return _c +} + +// NewMockExporter creates a new instance of MockExporter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockExporter(t interface { + mock.TestingT + Cleanup(func()) +}) *MockExporter { + mock := &MockExporter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mock/exporter/mock_HttpClient.go b/mock/exporter/mock_HttpClient.go new file mode 100644 index 0000000..45d8026 --- /dev/null +++ b/mock/exporter/mock_HttpClient.go @@ -0,0 +1,94 @@ +// Code generated by mockery v2.42.0. DO NOT EDIT. + +package exporter + +import ( + http "net/http" + + mock "github.com/stretchr/testify/mock" +) + +// MockHttpClient is an autogenerated mock type for the HttpClient type +type MockHttpClient struct { + mock.Mock +} + +type MockHttpClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockHttpClient) EXPECT() *MockHttpClient_Expecter { + return &MockHttpClient_Expecter{mock: &_m.Mock} +} + +// Do provides a mock function with given fields: req +func (_m *MockHttpClient) Do(req *http.Request) (*http.Response, error) { + ret := _m.Called(req) + + if len(ret) == 0 { + panic("no return value specified for Do") + } + + var r0 *http.Response + var r1 error + if rf, ok := ret.Get(0).(func(*http.Request) (*http.Response, error)); ok { + return rf(req) + } + if rf, ok := ret.Get(0).(func(*http.Request) *http.Response); ok { + r0 = rf(req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Response) + } + } + + if rf, ok := ret.Get(1).(func(*http.Request) error); ok { + r1 = rf(req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockHttpClient_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do' +type MockHttpClient_Do_Call struct { + *mock.Call +} + +// Do is a helper method to define mock.On call +// - req *http.Request +func (_e *MockHttpClient_Expecter) Do(req interface{}) *MockHttpClient_Do_Call { + return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", req)} +} + +func (_c *MockHttpClient_Do_Call) Run(run func(req *http.Request)) *MockHttpClient_Do_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*http.Request)) + }) + return _c +} + +func (_c *MockHttpClient_Do_Call) Return(_a0 *http.Response, _a1 error) *MockHttpClient_Do_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockHttpClient_Do_Call) RunAndReturn(run func(*http.Request) (*http.Response, error)) *MockHttpClient_Do_Call { + _c.Call.Return(run) + return _c +} + +// NewMockHttpClient creates a new instance of MockHttpClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockHttpClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockHttpClient { + mock := &MockHttpClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mock/exporter/mock_MetricMapper.go b/mock/exporter/mock_MetricMapper.go new file mode 100644 index 0000000..a3eaf38 --- /dev/null +++ b/mock/exporter/mock_MetricMapper.go @@ -0,0 +1,88 @@ +// Code generated by mockery v2.42.0. DO NOT EDIT. + +package exporter + +import ( + exporter "github.com/castai/gpu-metrics-exporter/internal/exporter" + mock "github.com/stretchr/testify/mock" + + pb "github.com/castai/gpu-metrics-exporter/pb" + + time "time" +) + +// MockMetricMapper is an autogenerated mock type for the MetricMapper type +type MockMetricMapper struct { + mock.Mock +} + +type MockMetricMapper_Expecter struct { + mock *mock.Mock +} + +func (_m *MockMetricMapper) EXPECT() *MockMetricMapper_Expecter { + return &MockMetricMapper_Expecter{mock: &_m.Mock} +} + +// Map provides a mock function with given fields: metrics, ts +func (_m *MockMetricMapper) Map(metrics []exporter.MetricFamiliyMap, ts time.Time) *pb.MetricsBatch { + ret := _m.Called(metrics, ts) + + if len(ret) == 0 { + panic("no return value specified for Map") + } + + var r0 *pb.MetricsBatch + if rf, ok := ret.Get(0).(func([]exporter.MetricFamiliyMap, time.Time) *pb.MetricsBatch); ok { + r0 = rf(metrics, ts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*pb.MetricsBatch) + } + } + + return r0 +} + +// MockMetricMapper_Map_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Map' +type MockMetricMapper_Map_Call struct { + *mock.Call +} + +// Map is a helper method to define mock.On call +// - metrics []exporter.MetricFamiliyMap +// - ts time.Time +func (_e *MockMetricMapper_Expecter) Map(metrics interface{}, ts interface{}) *MockMetricMapper_Map_Call { + return &MockMetricMapper_Map_Call{Call: _e.mock.On("Map", metrics, ts)} +} + +func (_c *MockMetricMapper_Map_Call) Run(run func(metrics []exporter.MetricFamiliyMap, ts time.Time)) *MockMetricMapper_Map_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]exporter.MetricFamiliyMap), args[1].(time.Time)) + }) + return _c +} + +func (_c *MockMetricMapper_Map_Call) Return(_a0 *pb.MetricsBatch) *MockMetricMapper_Map_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockMetricMapper_Map_Call) RunAndReturn(run func([]exporter.MetricFamiliyMap, time.Time) *pb.MetricsBatch) *MockMetricMapper_Map_Call { + _c.Call.Return(run) + return _c +} + +// NewMockMetricMapper creates a new instance of MockMetricMapper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockMetricMapper(t interface { + mock.TestingT + Cleanup(func()) +}) *MockMetricMapper { + mock := &MockMetricMapper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mock/exporter/mock_Scraper.go b/mock/exporter/mock_Scraper.go new file mode 100644 index 0000000..8003843 --- /dev/null +++ b/mock/exporter/mock_Scraper.go @@ -0,0 +1,96 @@ +// Code generated by mockery v2.42.0. DO NOT EDIT. + +package exporter + +import ( + context "context" + + exporter "github.com/castai/gpu-metrics-exporter/internal/exporter" + mock "github.com/stretchr/testify/mock" +) + +// MockScraper is an autogenerated mock type for the Scraper type +type MockScraper struct { + mock.Mock +} + +type MockScraper_Expecter struct { + mock *mock.Mock +} + +func (_m *MockScraper) EXPECT() *MockScraper_Expecter { + return &MockScraper_Expecter{mock: &_m.Mock} +} + +// Scrape provides a mock function with given fields: ctx, urls +func (_m *MockScraper) Scrape(ctx context.Context, urls []string) ([]exporter.MetricFamiliyMap, error) { + ret := _m.Called(ctx, urls) + + if len(ret) == 0 { + panic("no return value specified for Scrape") + } + + var r0 []exporter.MetricFamiliyMap + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]exporter.MetricFamiliyMap, error)); ok { + return rf(ctx, urls) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []exporter.MetricFamiliyMap); ok { + r0 = rf(ctx, urls) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]exporter.MetricFamiliyMap) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, urls) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockScraper_Scrape_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Scrape' +type MockScraper_Scrape_Call struct { + *mock.Call +} + +// Scrape is a helper method to define mock.On call +// - ctx context.Context +// - urls []string +func (_e *MockScraper_Expecter) Scrape(ctx interface{}, urls interface{}) *MockScraper_Scrape_Call { + return &MockScraper_Scrape_Call{Call: _e.mock.On("Scrape", ctx, urls)} +} + +func (_c *MockScraper_Scrape_Call) Run(run func(ctx context.Context, urls []string)) *MockScraper_Scrape_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]string)) + }) + return _c +} + +func (_c *MockScraper_Scrape_Call) Return(_a0 []exporter.MetricFamiliyMap, _a1 error) *MockScraper_Scrape_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockScraper_Scrape_Call) RunAndReturn(run func(context.Context, []string) ([]exporter.MetricFamiliyMap, error)) *MockScraper_Scrape_Call { + _c.Call.Return(run) + return _c +} + +// NewMockScraper creates a new instance of MockScraper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockScraper(t interface { + mock.TestingT + Cleanup(func()) +}) *MockScraper { + mock := &MockScraper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pb/metrics.pb.go b/pb/metrics.pb.go new file mode 100644 index 0000000..f9de810 --- /dev/null +++ b/pb/metrics.pb.go @@ -0,0 +1,380 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v4.25.2 +// source: pb/metrics.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Metric struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Measurements []*Metric_Measurement `protobuf:"bytes,2,rep,name=measurements,proto3" json:"measurements,omitempty"` +} + +func (x *Metric) Reset() { + *x = Metric{} + if protoimpl.UnsafeEnabled { + mi := &file_pb_metrics_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Metric) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Metric) ProtoMessage() {} + +func (x *Metric) ProtoReflect() protoreflect.Message { + mi := &file_pb_metrics_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Metric.ProtoReflect.Descriptor instead. +func (*Metric) Descriptor() ([]byte, []int) { + return file_pb_metrics_proto_rawDescGZIP(), []int{0} +} + +func (x *Metric) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Metric) GetMeasurements() []*Metric_Measurement { + if x != nil { + return x.Measurements + } + return nil +} + +type MetricsBatch struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Metrics []*Metric `protobuf:"bytes,1,rep,name=metrics,proto3" json:"metrics,omitempty"` +} + +func (x *MetricsBatch) Reset() { + *x = MetricsBatch{} + if protoimpl.UnsafeEnabled { + mi := &file_pb_metrics_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MetricsBatch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MetricsBatch) ProtoMessage() {} + +func (x *MetricsBatch) ProtoReflect() protoreflect.Message { + mi := &file_pb_metrics_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MetricsBatch.ProtoReflect.Descriptor instead. +func (*MetricsBatch) Descriptor() ([]byte, []int) { + return file_pb_metrics_proto_rawDescGZIP(), []int{1} +} + +func (x *MetricsBatch) GetMetrics() []*Metric { + if x != nil { + return x.Metrics + } + return nil +} + +type Metric_Label struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Metric_Label) Reset() { + *x = Metric_Label{} + if protoimpl.UnsafeEnabled { + mi := &file_pb_metrics_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Metric_Label) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Metric_Label) ProtoMessage() {} + +func (x *Metric_Label) ProtoReflect() protoreflect.Message { + mi := &file_pb_metrics_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Metric_Label.ProtoReflect.Descriptor instead. +func (*Metric_Label) Descriptor() ([]byte, []int) { + return file_pb_metrics_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *Metric_Label) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Metric_Label) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type Metric_Measurement struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value float64 `protobuf:"fixed64,1,opt,name=value,proto3" json:"value,omitempty"` + Ts *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=ts,proto3" json:"ts,omitempty"` + Labels []*Metric_Label `protobuf:"bytes,3,rep,name=labels,proto3" json:"labels,omitempty"` +} + +func (x *Metric_Measurement) Reset() { + *x = Metric_Measurement{} + if protoimpl.UnsafeEnabled { + mi := &file_pb_metrics_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Metric_Measurement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Metric_Measurement) ProtoMessage() {} + +func (x *Metric_Measurement) ProtoReflect() protoreflect.Message { + mi := &file_pb_metrics_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Metric_Measurement.ProtoReflect.Descriptor instead. +func (*Metric_Measurement) Descriptor() ([]byte, []int) { + return file_pb_metrics_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *Metric_Measurement) GetValue() float64 { + if x != nil { + return x.Value + } + return 0 +} + +func (x *Metric_Measurement) GetTs() *timestamppb.Timestamp { + if x != nil { + return x.Ts + } + return nil +} + +func (x *Metric_Measurement) GetLabels() []*Metric_Label { + if x != nil { + return x.Labels + } + return nil +} + +var File_pb_metrics_proto protoreflect.FileDescriptor + +var file_pb_metrics_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x70, 0x62, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x80, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0c, 0x6d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x31, 0x0a, 0x05, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x76, + 0x0a, 0x0b, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x02, 0x74, 0x73, 0x12, + 0x25, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0d, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x06, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x22, 0x31, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x21, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x61, 0x69, 0x2f, 0x67, + 0x70, 0x75, 0x2d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x65, 0x78, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x72, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_pb_metrics_proto_rawDescOnce sync.Once + file_pb_metrics_proto_rawDescData = file_pb_metrics_proto_rawDesc +) + +func file_pb_metrics_proto_rawDescGZIP() []byte { + file_pb_metrics_proto_rawDescOnce.Do(func() { + file_pb_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_pb_metrics_proto_rawDescData) + }) + return file_pb_metrics_proto_rawDescData +} + +var file_pb_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_pb_metrics_proto_goTypes = []interface{}{ + (*Metric)(nil), // 0: Metric + (*MetricsBatch)(nil), // 1: MetricsBatch + (*Metric_Label)(nil), // 2: Metric.Label + (*Metric_Measurement)(nil), // 3: Metric.Measurement + (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp +} +var file_pb_metrics_proto_depIdxs = []int32{ + 3, // 0: Metric.measurements:type_name -> Metric.Measurement + 0, // 1: MetricsBatch.metrics:type_name -> Metric + 4, // 2: Metric.Measurement.ts:type_name -> google.protobuf.Timestamp + 2, // 3: Metric.Measurement.labels:type_name -> Metric.Label + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_pb_metrics_proto_init() } +func file_pb_metrics_proto_init() { + if File_pb_metrics_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pb_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Metric); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pb_metrics_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MetricsBatch); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pb_metrics_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Metric_Label); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pb_metrics_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Metric_Measurement); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_pb_metrics_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_pb_metrics_proto_goTypes, + DependencyIndexes: file_pb_metrics_proto_depIdxs, + MessageInfos: file_pb_metrics_proto_msgTypes, + }.Build() + File_pb_metrics_proto = out.File + file_pb_metrics_proto_rawDesc = nil + file_pb_metrics_proto_goTypes = nil + file_pb_metrics_proto_depIdxs = nil +} diff --git a/pb/metrics.proto b/pb/metrics.proto new file mode 100644 index 0000000..fff237d --- /dev/null +++ b/pb/metrics.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/castai/gpu-metrics-exporter/pb"; + +message Metric { + string name = 1; + repeated Measurement measurements = 2; + + message Label { + string name = 1; + string value = 2; + } + + message Measurement { + double value = 1; + google.protobuf.Timestamp ts = 2; + repeated Label labels = 3; + } +} + +message MetricsBatch { + repeated Metric metrics = 1; +} \ No newline at end of file From 0a9a9208963811403dcb9d7653cc4d7518b3d561 Mon Sep 17 00:00:00 2001 From: Ivaylo Papratilov Date: Thu, 7 Mar 2024 23:45:14 +0200 Subject: [PATCH 3/5] Update internal/exporter/mapper.go Co-authored-by: Blagoj Atanasovski <46002495+atanasovskib@users.noreply.github.com> --- internal/exporter/mapper.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/internal/exporter/mapper.go b/internal/exporter/mapper.go index 0239a56..7203756 100644 --- a/internal/exporter/mapper.go +++ b/internal/exporter/mapper.go @@ -44,20 +44,18 @@ func (p metricMapper) Map(metricFamilyMaps []MetricFamiliyMap, ts time.Time) *pb Value: *l.Value, }) } + var newValue float64 switch t { case "COUNTER": - metric.Measurements = append(metric.Measurements, &pb.Metric_Measurement{ - Value: *m.GetCounter().Value, - Ts: timestamppb.New(ts), - Labels: labels, - }) + newValue = *m.GetCounter().Value case "GAUGE": - metric.Measurements = append(metric.Measurements, &pb.Metric_Measurement{ - Value: *m.GetGauge().Value, + newValue = *m.GetGauge().Value, + } + metric.Measurements = append(metric.Measurements, newMeasurement = &pb.Metric_Measurement{ + Value: newValue, Ts: timestamppb.New(ts), Labels: labels, }) - } } } } From 671d2e177e7bd7ad40f0cfc8f8b1405de76b8644 Mon Sep 17 00:00:00 2001 From: Ivaylo Papratilov Date: Thu, 7 Mar 2024 23:45:26 +0200 Subject: [PATCH 4/5] Update internal/exporter/scraper.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ciprian Focșăneanu --- internal/exporter/scraper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/exporter/scraper.go b/internal/exporter/scraper.go index be7a5d2..f45c922 100644 --- a/internal/exporter/scraper.go +++ b/internal/exporter/scraper.go @@ -17,7 +17,7 @@ const ( maxConcurrentScrapes = 15 ) -type MetricFamiliyMap map[string]*dto.MetricFamily +type MetricFamilyMap map[string]*dto.MetricFamily type Scraper interface { Scrape(ctx context.Context, urls []string) ([]MetricFamiliyMap, error) From 811de8d6161d7ffbeb3a4ecb3fac0f5d0ad7b1db Mon Sep 17 00:00:00 2001 From: Ivaylo Papratilov Date: Thu, 7 Mar 2024 23:55:21 +0200 Subject: [PATCH 5/5] Address PR comments --- cmd/main.go | 5 +++-- internal/config/config.go | 16 ++++++++------ internal/exporter/exporter_test.go | 2 +- internal/exporter/mapper.go | 17 ++++++++------- internal/exporter/mapper_test.go | 6 +++--- internal/exporter/scraper.go | 8 +++---- internal/exporter/scraper_test.go | 6 +++--- mock/exporter/mock_HttpClient.go | 34 +++++++++++++++--------------- mock/exporter/mock_MetricMapper.go | 12 +++++------ mock/exporter/mock_Scraper.go | 14 ++++++------ 10 files changed, 62 insertions(+), 58 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index dd3cb31..4da7c2e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -82,14 +82,15 @@ func run(cfg *config.Config, log logrus.FieldLogger) error { ex := exporter.NewExporter(exporter.Config{ ExportInterval: cfg.ExportInterval, Selector: labelSelector.String(), - DCGMExporterPort: 9400, - DCGMExporterPath: "/metrics", + DCGMExporterPort: cfg.DCGMPort, + DCGMExporterPath: cfg.DCGMMetricsEndpoint, Enabled: true, }, clientset, log, scraper, mapper) go func() { if err := ex.Start(ctx); err != nil && !errors.Is(err, context.Canceled) { log.Errorf("exporter stopped with error %v", err) + cancel() } }() diff --git a/internal/config/config.go b/internal/config/config.go index e810bd2..2f07ff7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,13 +7,15 @@ import ( ) type Config struct { - HTTPListenPort int `envconfig:"HTTP_LISTEN_PORT" default:"6061"` - LogLevel string `envconfig:"LOG_LEVEL" default:"info"` - KubeConfigPath string `envconfig:"KUBE_CONFIG_PATH"` - DCGMLabels map[string]string `envconfig:"DCGM_LABELS" default:"app.kubernetes.io/component:dcgm-exporter"` - ExportInterval time.Duration `envconfig:"EXPORT_INTERVAL" default:"15s"` - CastAPI string `envconfig:"CAST_API" default:"https://api.cast.ai"` - APIToken string `envconfig:"API_TOKEN"` + HTTPListenPort int `envconfig:"HTTP_LISTEN_PORT" default:"6061"` + LogLevel string `envconfig:"LOG_LEVEL" default:"info"` + KubeConfigPath string `envconfig:"KUBE_CONFIG_PATH"` + DCGMLabels map[string]string `envconfig:"DCGM_LABELS" default:"app.kubernetes.io/component:dcgm-exporter"` + DCGMPort int `envconfig:"DCGM_PORT" default:"9400"` + DCGMMetricsEndpoint string `envconfig:"DCGM_METRICS_ENDPOINT" default:"/metrics"` + ExportInterval time.Duration `envconfig:"EXPORT_INTERVAL" default:"15s"` + CastAPI string `envconfig:"CAST_API" default:"https://api.cast.ai"` + APIToken string `envconfig:"API_TOKEN"` } func GetFromEnvironment() (*Config, error) { diff --git a/internal/exporter/exporter_test.go b/internal/exporter/exporter_test.go index 807c1ed..d4ac263 100644 --- a/internal/exporter/exporter_test.go +++ b/internal/exporter/exporter_test.go @@ -48,7 +48,7 @@ func TestExporter_Running(t *testing.T) { ex := exporter.NewExporter(config, kubeClient, log, scraper, mapper) ex.Enable() - metricFamilies := []exporter.MetricFamiliyMap{ + metricFamilies := []exporter.MetricFamilyMap{ { "test_gauge": { Type: dto.MetricType_GAUGE.Enum(), diff --git a/internal/exporter/mapper.go b/internal/exporter/mapper.go index 7203756..541ca6c 100644 --- a/internal/exporter/mapper.go +++ b/internal/exporter/mapper.go @@ -9,7 +9,7 @@ import ( ) type MetricMapper interface { - Map(metrics []MetricFamiliyMap, ts time.Time) *pb.MetricsBatch + Map(metrics []MetricFamilyMap, ts time.Time) *pb.MetricsBatch } type metricMapper struct{} @@ -18,7 +18,7 @@ func NewMapper() MetricMapper { return &metricMapper{} } -func (p metricMapper) Map(metricFamilyMaps []MetricFamiliyMap, ts time.Time) *pb.MetricsBatch { +func (p metricMapper) Map(metricFamilyMaps []MetricFamilyMap, ts time.Time) *pb.MetricsBatch { metrics := &pb.MetricsBatch{} metricsMap := make(map[string]*pb.Metric) for _, familyMap := range metricFamilyMaps { @@ -49,13 +49,14 @@ func (p metricMapper) Map(metricFamilyMaps []MetricFamiliyMap, ts time.Time) *pb case "COUNTER": newValue = *m.GetCounter().Value case "GAUGE": - newValue = *m.GetGauge().Value, + newValue = *m.GetGauge().Value } - metric.Measurements = append(metric.Measurements, newMeasurement = &pb.Metric_Measurement{ - Value: newValue, - Ts: timestamppb.New(ts), - Labels: labels, - }) + + metric.Measurements = append(metric.Measurements, &pb.Metric_Measurement{ + Value: newValue, + Ts: timestamppb.New(ts), + Labels: labels, + }) } } } diff --git a/internal/exporter/mapper_test.go b/internal/exporter/mapper_test.go index 5a97d18..45dac64 100644 --- a/internal/exporter/mapper_test.go +++ b/internal/exporter/mapper_test.go @@ -30,7 +30,7 @@ func TestMetricMapper_Map(t *testing.T) { t.Run("empty input yields empty MetricsBatch", func(t *testing.T) { ts := time.Now() - metricFamilyMaps := []exporter.MetricFamiliyMap{} + metricFamilyMaps := []exporter.MetricFamilyMap{} got := mapper.Map(metricFamilyMaps, ts) expected := &pb.MetricsBatch{} @@ -41,7 +41,7 @@ func TestMetricMapper_Map(t *testing.T) { t.Run("metric familiy which is not enabled is skipped", func(t *testing.T) { ts := time.Now() - metricFamilyMaps := []exporter.MetricFamiliyMap{ + metricFamilyMaps := []exporter.MetricFamilyMap{ { "test_gauge": { Type: dto.MetricType_GAUGE.Enum(), @@ -66,7 +66,7 @@ func TestMetricMapper_Map(t *testing.T) { t.Run("enabled metric family is included", func(t *testing.T) { ts := time.Now() - metricFamilyMaps := []exporter.MetricFamiliyMap{ + metricFamilyMaps := []exporter.MetricFamilyMap{ { "test_gauge": { Type: dto.MetricType_GAUGE.Enum(), diff --git a/internal/exporter/scraper.go b/internal/exporter/scraper.go index f45c922..510dee3 100644 --- a/internal/exporter/scraper.go +++ b/internal/exporter/scraper.go @@ -20,7 +20,7 @@ const ( type MetricFamilyMap map[string]*dto.MetricFamily type Scraper interface { - Scrape(ctx context.Context, urls []string) ([]MetricFamiliyMap, error) + Scrape(ctx context.Context, urls []string) ([]MetricFamilyMap, error) } type HTTPClient interface { @@ -28,7 +28,7 @@ type HTTPClient interface { } type result struct { - metricFamilyMap MetricFamiliyMap + metricFamilyMap MetricFamilyMap err error ts time.Time } @@ -46,7 +46,7 @@ func NewScraper(httpClient HTTPClient, log logrus.FieldLogger) Scraper { } } -func (s scraper) Scrape(ctx context.Context, urls []string) ([]MetricFamiliyMap, error) { +func (s scraper) Scrape(ctx context.Context, urls []string) ([]MetricFamilyMap, error) { var g errgroup.Group g.SetLimit(maxConcurrentScrapes) @@ -78,7 +78,7 @@ func (s scraper) Scrape(ctx context.Context, urls []string) ([]MetricFamiliyMap, close(resultsChan) }() - metrics := make([]MetricFamiliyMap, 0, len(urls)) + metrics := make([]MetricFamilyMap, 0, len(urls)) for result := range resultsChan { if result.err != nil { s.log.Error(result.err) diff --git a/internal/exporter/scraper_test.go b/internal/exporter/scraper_test.go index 8fb22e6..9a3ad1f 100644 --- a/internal/exporter/scraper_test.go +++ b/internal/exporter/scraper_test.go @@ -28,7 +28,7 @@ func TestScraper_Scrape(t *testing.T) { log := logrus.New() t.Run("scrapes metrics without error", func(t *testing.T) { - httpClient := mocks.NewMockHttpClient(t) + httpClient := mocks.NewMockHTTPClient(t) scraper := exporter.NewScraper(httpClient, log) response1 := &http.Response{ @@ -76,7 +76,7 @@ func TestScraper_Scrape(t *testing.T) { }) t.Run("partially scrapes metrics when some exporter returns non-200 code", func(t *testing.T) { - httpClient := mocks.NewMockHttpClient(t) + httpClient := mocks.NewMockHTTPClient(t) scraper := exporter.NewScraper(httpClient, log) response := &http.Response{ @@ -123,7 +123,7 @@ func TestScraper_Scrape(t *testing.T) { }) t.Run("partially scrapes metrics when some exporter cannot be scraped", func(t *testing.T) { - httpClient := mocks.NewMockHttpClient(t) + httpClient := mocks.NewMockHTTPClient(t) scraper := exporter.NewScraper(httpClient, log) response := &http.Response{ diff --git a/mock/exporter/mock_HttpClient.go b/mock/exporter/mock_HttpClient.go index 45d8026..7d078e7 100644 --- a/mock/exporter/mock_HttpClient.go +++ b/mock/exporter/mock_HttpClient.go @@ -8,21 +8,21 @@ import ( mock "github.com/stretchr/testify/mock" ) -// MockHttpClient is an autogenerated mock type for the HttpClient type -type MockHttpClient struct { +// MockHTTPClient is an autogenerated mock type for the HTTPClient type +type MockHTTPClient struct { mock.Mock } -type MockHttpClient_Expecter struct { +type MockHTTPClient_Expecter struct { mock *mock.Mock } -func (_m *MockHttpClient) EXPECT() *MockHttpClient_Expecter { - return &MockHttpClient_Expecter{mock: &_m.Mock} +func (_m *MockHTTPClient) EXPECT() *MockHTTPClient_Expecter { + return &MockHTTPClient_Expecter{mock: &_m.Mock} } // Do provides a mock function with given fields: req -func (_m *MockHttpClient) Do(req *http.Request) (*http.Response, error) { +func (_m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) { ret := _m.Called(req) if len(ret) == 0 { @@ -51,41 +51,41 @@ func (_m *MockHttpClient) Do(req *http.Request) (*http.Response, error) { return r0, r1 } -// MockHttpClient_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do' -type MockHttpClient_Do_Call struct { +// MockHTTPClient_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do' +type MockHTTPClient_Do_Call struct { *mock.Call } // Do is a helper method to define mock.On call // - req *http.Request -func (_e *MockHttpClient_Expecter) Do(req interface{}) *MockHttpClient_Do_Call { - return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", req)} +func (_e *MockHTTPClient_Expecter) Do(req interface{}) *MockHTTPClient_Do_Call { + return &MockHTTPClient_Do_Call{Call: _e.mock.On("Do", req)} } -func (_c *MockHttpClient_Do_Call) Run(run func(req *http.Request)) *MockHttpClient_Do_Call { +func (_c *MockHTTPClient_Do_Call) Run(run func(req *http.Request)) *MockHTTPClient_Do_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(*http.Request)) }) return _c } -func (_c *MockHttpClient_Do_Call) Return(_a0 *http.Response, _a1 error) *MockHttpClient_Do_Call { +func (_c *MockHTTPClient_Do_Call) Return(_a0 *http.Response, _a1 error) *MockHTTPClient_Do_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockHttpClient_Do_Call) RunAndReturn(run func(*http.Request) (*http.Response, error)) *MockHttpClient_Do_Call { +func (_c *MockHTTPClient_Do_Call) RunAndReturn(run func(*http.Request) (*http.Response, error)) *MockHTTPClient_Do_Call { _c.Call.Return(run) return _c } -// NewMockHttpClient creates a new instance of MockHttpClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewMockHTTPClient creates a new instance of MockHTTPClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewMockHttpClient(t interface { +func NewMockHTTPClient(t interface { mock.TestingT Cleanup(func()) -}) *MockHttpClient { - mock := &MockHttpClient{} +}) *MockHTTPClient { + mock := &MockHTTPClient{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/mock/exporter/mock_MetricMapper.go b/mock/exporter/mock_MetricMapper.go index a3eaf38..6dc6527 100644 --- a/mock/exporter/mock_MetricMapper.go +++ b/mock/exporter/mock_MetricMapper.go @@ -25,7 +25,7 @@ func (_m *MockMetricMapper) EXPECT() *MockMetricMapper_Expecter { } // Map provides a mock function with given fields: metrics, ts -func (_m *MockMetricMapper) Map(metrics []exporter.MetricFamiliyMap, ts time.Time) *pb.MetricsBatch { +func (_m *MockMetricMapper) Map(metrics []exporter.MetricFamilyMap, ts time.Time) *pb.MetricsBatch { ret := _m.Called(metrics, ts) if len(ret) == 0 { @@ -33,7 +33,7 @@ func (_m *MockMetricMapper) Map(metrics []exporter.MetricFamiliyMap, ts time.Tim } var r0 *pb.MetricsBatch - if rf, ok := ret.Get(0).(func([]exporter.MetricFamiliyMap, time.Time) *pb.MetricsBatch); ok { + if rf, ok := ret.Get(0).(func([]exporter.MetricFamilyMap, time.Time) *pb.MetricsBatch); ok { r0 = rf(metrics, ts) } else { if ret.Get(0) != nil { @@ -50,15 +50,15 @@ type MockMetricMapper_Map_Call struct { } // Map is a helper method to define mock.On call -// - metrics []exporter.MetricFamiliyMap +// - metrics []exporter.MetricFamilyMap // - ts time.Time func (_e *MockMetricMapper_Expecter) Map(metrics interface{}, ts interface{}) *MockMetricMapper_Map_Call { return &MockMetricMapper_Map_Call{Call: _e.mock.On("Map", metrics, ts)} } -func (_c *MockMetricMapper_Map_Call) Run(run func(metrics []exporter.MetricFamiliyMap, ts time.Time)) *MockMetricMapper_Map_Call { +func (_c *MockMetricMapper_Map_Call) Run(run func(metrics []exporter.MetricFamilyMap, ts time.Time)) *MockMetricMapper_Map_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].([]exporter.MetricFamiliyMap), args[1].(time.Time)) + run(args[0].([]exporter.MetricFamilyMap), args[1].(time.Time)) }) return _c } @@ -68,7 +68,7 @@ func (_c *MockMetricMapper_Map_Call) Return(_a0 *pb.MetricsBatch) *MockMetricMap return _c } -func (_c *MockMetricMapper_Map_Call) RunAndReturn(run func([]exporter.MetricFamiliyMap, time.Time) *pb.MetricsBatch) *MockMetricMapper_Map_Call { +func (_c *MockMetricMapper_Map_Call) RunAndReturn(run func([]exporter.MetricFamilyMap, time.Time) *pb.MetricsBatch) *MockMetricMapper_Map_Call { _c.Call.Return(run) return _c } diff --git a/mock/exporter/mock_Scraper.go b/mock/exporter/mock_Scraper.go index 8003843..656b044 100644 --- a/mock/exporter/mock_Scraper.go +++ b/mock/exporter/mock_Scraper.go @@ -23,23 +23,23 @@ func (_m *MockScraper) EXPECT() *MockScraper_Expecter { } // Scrape provides a mock function with given fields: ctx, urls -func (_m *MockScraper) Scrape(ctx context.Context, urls []string) ([]exporter.MetricFamiliyMap, error) { +func (_m *MockScraper) Scrape(ctx context.Context, urls []string) ([]exporter.MetricFamilyMap, error) { ret := _m.Called(ctx, urls) if len(ret) == 0 { panic("no return value specified for Scrape") } - var r0 []exporter.MetricFamiliyMap + var r0 []exporter.MetricFamilyMap var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []string) ([]exporter.MetricFamiliyMap, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]exporter.MetricFamilyMap, error)); ok { return rf(ctx, urls) } - if rf, ok := ret.Get(0).(func(context.Context, []string) []exporter.MetricFamiliyMap); ok { + if rf, ok := ret.Get(0).(func(context.Context, []string) []exporter.MetricFamilyMap); ok { r0 = rf(ctx, urls) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]exporter.MetricFamiliyMap) + r0 = ret.Get(0).([]exporter.MetricFamilyMap) } } @@ -71,12 +71,12 @@ func (_c *MockScraper_Scrape_Call) Run(run func(ctx context.Context, urls []stri return _c } -func (_c *MockScraper_Scrape_Call) Return(_a0 []exporter.MetricFamiliyMap, _a1 error) *MockScraper_Scrape_Call { +func (_c *MockScraper_Scrape_Call) Return(_a0 []exporter.MetricFamilyMap, _a1 error) *MockScraper_Scrape_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockScraper_Scrape_Call) RunAndReturn(run func(context.Context, []string) ([]exporter.MetricFamiliyMap, error)) *MockScraper_Scrape_Call { +func (_c *MockScraper_Scrape_Call) RunAndReturn(run func(context.Context, []string) ([]exporter.MetricFamilyMap, error)) *MockScraper_Scrape_Call { _c.Call.Return(run) return _c }