├── VERSION ├── MAINTAINERS.md ├── assets ├── prometheus │ ├── README.md │ └── rules │ │ └── README.md └── systemd │ └── add_service.sh ├── .gitignore ├── go.mod ├── .github └── workflows │ ├── goreleaser.yml │ └── main.yml ├── .goreleaser.yml ├── README.md ├── go.sum ├── pkg └── ovs_exporter │ ├── ovs_exporter_test.go │ └── ovs_exporter.go ├── Makefile ├── cmd └── ovs_exporter │ └── main.go └── LICENSE /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.7 -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | * Paul Greenberg 2 | -------------------------------------------------------------------------------- /assets/prometheus/README.md: -------------------------------------------------------------------------------- 1 | This directory contains files related to Prometheus integration. 2 | -------------------------------------------------------------------------------- /assets/prometheus/rules/README.md: -------------------------------------------------------------------------------- 1 | This directory contains Prometheus rules for OVS exporter. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage/ 2 | ovs_exporter.test 3 | /.build 4 | /.release 5 | /.tarballs 6 | /ovs-exporter 7 | *.tar.gz 8 | bin/ 9 | dist/ 10 | TODO 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/greenpau/ovs_exporter 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-kit/log v0.2.1 7 | github.com/greenpau/ovsdb v1.0.4 8 | github.com/prometheus/client_golang v1.16.0 9 | github.com/prometheus/common v0.44.0 10 | ) 11 | 12 | require ( 13 | github.com/beorn7/perks v1.0.1 // indirect 14 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 15 | github.com/go-logfmt/logfmt v0.5.1 // indirect 16 | github.com/golang/protobuf v1.5.3 // indirect 17 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 18 | github.com/prometheus/client_model v0.4.0 // indirect 19 | github.com/prometheus/procfs v0.10.1 // indirect 20 | golang.org/x/sys v0.8.0 // indirect 21 | google.golang.org/protobuf v1.30.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: {} 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | goreleaser: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | - run: git fetch --force --tags 20 | - uses: actions/setup-go@v3 21 | with: 22 | go-version: '>=1.20.7' 23 | cache: true 24 | - uses: goreleaser/goreleaser-action@v4 25 | with: 26 | distribution: goreleaser 27 | version: latest 28 | args: release --rm-dist 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | GOPATH: /home/runner/go 32 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | --- 2 | before: 3 | hooks: 4 | - go mod tidy 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - linux 10 | ldflags: 11 | - -w -s 12 | -X github.com/prometheus/common/version.Version={{ .Version }} 13 | -X github.com/prometheus/common/version.Revision={{ .Commit }} 14 | -X github.com/prometheus/common/version.Branch={{ .Branch }} 15 | -X github.com/prometheus/common/version.BuildUser=greenpau 16 | -X github.com/prometheus/common/version.BuildDate={{ .Date }} 17 | -X github.com/greenpau/ovs_exporter/pkg/ovs_exporter.appVersion={{ .Version }} 18 | -X github.com/greenpau/ovs_exporter/pkg/ovs_exporter.gitBranch={{ .Branch }} 19 | -X github.com/greenpau/ovs_exporter/pkg/ovs_exporter.gitCommit={{ .Commit }} 20 | -X github.com/greenpau/ovs_exporter/pkg/ovs_exporter.buildUser=greenpau 21 | -X github.com/greenpau/ovs_exporter/pkg/ovs_exporter.buildDate={{ .Date }} 22 | main: './cmd/ovs_exporter' 23 | asmflags: 24 | - 'all=-trimpath={{.Env.GOPATH}}' 25 | gcflags: 26 | - 'all=-trimpath={{.Env.GOPATH}}' 27 | binary: ovs-exporter 28 | archives: 29 | - name_template: "ovs-exporter_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" 30 | checksum: 31 | name_template: 'checksums.txt' 32 | snapshot: 33 | name_template: "{{ .Version }}" 34 | changelog: 35 | sort: asc 36 | filters: 37 | exclude: 38 | - '^docs:' 39 | - '^test:' 40 | -------------------------------------------------------------------------------- /assets/systemd/add_service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | MYAPP=ovs-exporter 6 | MYAPP_USER=ovs_exporter 7 | MYAPP_GROUP=ovs_exporter 8 | MYAPP_SERVICE=ovs-exporter 9 | MYAPP_BIN=/usr/sbin/ovs-exporter 10 | MYAPP_DESCRIPTION="Prometheus OVS Exporter" 11 | MYAPP_CONF="/etc/sysconfig/${MYAPP_SERVICE}" 12 | 13 | if [ -f "./${MYAPP}" ]; then 14 | rm -rf $MYAPP_BIN 15 | cp ./${MYAPP} ${MYAPP_BIN} 16 | fi 17 | 18 | if getent group ${MYAPP_GROUP} >/dev/null; then 19 | printf "INFO: ${MYAPP_GROUP} group already exists\n" 20 | else 21 | printf "INFO: ${MYAPP_GROUP} group does not exist, creating ..." 22 | groupadd --system ${MYAPP_GROUP} 23 | fi 24 | 25 | if getent passwd ${MYAPP_USER} >/dev/null; then 26 | printf "INFO: ${MYAPP_USER} user already exists\n" 27 | else 28 | printf "INFO: ${MYAPP_USER} group does not exist, creating ..." 29 | useradd --system -d /var/lib/${MYAPP} -s /bin/bash -g ${MYAPP_GROUP} ${MYAPP_USER} 30 | fi 31 | 32 | mkdir -p /var/lib/${MYAPP} 33 | chown -R ${MYAPP_USER}:${MYAPP_GROUP} /var/lib/${MYAPP} 34 | 35 | cat << EOF > /usr/lib/systemd/system/${MYAPP_SERVICE}.service 36 | [Unit] 37 | Description=$MYAPP_DESCRIPTION 38 | After=network.target 39 | 40 | [Service] 41 | User=${MYAPP_USER} 42 | Group=${MYAPP_GROUP} 43 | EnvironmentFile=-${MYAPP_CONF} 44 | ExecStart=${MYAPP_BIN} \$OPTIONS 45 | Restart=on-failure 46 | 47 | [Install] 48 | WantedBy=multi-user.target 49 | EOF 50 | 51 | systemctl daemon-reload 52 | systemctl is-active --quiet ${MYAPP_SERVICE} && systemctl stop ${MYAPP_SERVICE} 53 | setcap cap_sys_admin,cap_sys_nice,cap_dac_override+ep ${MYAPP_BIN} || true 54 | systemctl enable ${MYAPP_SERVICE} 55 | systemctl start ${MYAPP_SERVICE} 56 | if systemctl is-active --quiet ${MYAPP_SERVICE}; then 57 | printf "INFO: ${MYAPP_SERVICE} service is running\n" 58 | else 59 | printf "FAIL: ${MYAPP_SERVICE} service is not running\n" 60 | systemctl status ${MYAPP_SERVICE} 61 | exit 1 62 | fi 63 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: build 3 | 4 | on: 5 | workflow_dispatch: {} 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | core: 15 | strategy: 16 | matrix: 17 | go-version: [1.20.x] 18 | platform: [ubuntu-latest] 19 | name: Build 20 | runs-on: ${{ matrix.platform }} 21 | env: 22 | GOBIN: /home/runner/.local/bin 23 | steps: 24 | - name: Install Go 25 | uses: actions/setup-go@v3 26 | with: 27 | go-version: ${{ matrix.go-version }} 28 | id: go 29 | - name: Check out code into the Go module directory 30 | uses: actions/checkout@v3 31 | - name: Amend Environment Path 32 | run: | 33 | mkdir -p /home/runner/.local/bin 34 | echo "/home/runner/.local/bin" >> $GITHUB_PATH 35 | - name: Setup Environment 36 | run: | 37 | mkdir -p .coverage 38 | echo "*** Current Directory ***" 39 | pwd 40 | echo "*** Environment Variables ***" 41 | env | sort 42 | echo "*** Executable Path ***" 43 | echo "$PATH" | tr ':' '\n' 44 | echo "*** Workspace Files ***" 45 | find . 46 | which make 47 | - name: Install prerequisites 48 | run: | 49 | sudo apt-get --assume-yes install make 50 | sudo apt-get --assume-yes install libnss3-tools 51 | sudo apt-get install openvswitch-switch 52 | sudo apt-get update 53 | - name: Install Go modules 54 | run: | 55 | make dep 56 | - name: Open vSwitch Checks 57 | run: | 58 | sudo ovs-vsctl --version 59 | sudo ovs-vsctl show 60 | sudo systemctl status openvswitch-switch.service 61 | sudo ovs-vsctl add-br br0 62 | sudo ovs-vsctl list bridge 63 | sudo ps -ef 64 | - name: Validate prerequisites 65 | run: | 66 | echo "*** Local binaries ***" 67 | find /home/runner/.local/bin 68 | - name: Run tests 69 | run: | 70 | make test 71 | - name: Generate coverage report 72 | run: make coverage 73 | - name: Upload coverage report 74 | uses: actions/upload-artifact@v3 75 | with: 76 | name: Test Coverage Report 77 | path: .coverage/coverage.html 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open Virtual Switch (OVS) Exporter 2 | 3 | 4 | 5 | Export Open Virtual Switch (OVS) data to Prometheus. 6 | 7 | ## Introduction 8 | 9 | This exporter exports metrics from the following OVS components: 10 | * OVS `vswitchd` service 11 | * `Open_vSwitch` database 12 | * OVN `ovn-controller` service 13 | 14 | ## Getting Started 15 | 16 | Run the following commands to install it: 17 | 18 | ```bash 19 | wget https://github.com/greenpau/ovs_exporter/releases/download/v1.0.4/ovs-exporter-1.0.4.linux-amd64.tar.gz 20 | tar xvzf ovs-exporter-1.0.4.linux-amd64.tar.gz 21 | cd ovs-exporter* 22 | ./install.sh 23 | cd .. 24 | rm -rf ovs-exporter-1.0.4.linux-amd64* 25 | systemctl status ovs-exporter -l 26 | curl -s localhost:9475/metrics | grep server_id 27 | ``` 28 | 29 | Run the following commands to build and test it: 30 | 31 | ```bash 32 | cd $GOPATH/src 33 | mkdir -p github.com/greenpau 34 | cd github.com/greenpau 35 | git clone https://github.com/greenpau/ovs_exporter.git 36 | cd ovs_exporter 37 | make 38 | make qtest 39 | ``` 40 | 41 | ## Exported Metrics 42 | 43 | | Metric | Meaning | Labels | 44 | | ------ | ------- | ------ | 45 | | `ovs_up` | Is OVS stack up (1) or is it down (0). | `system_id` | 46 | 47 | For example: 48 | 49 | ```bash 50 | $ curl localhost:9475/metrics | grep ovn 51 | # HELP ovs_up Is OVS stack up (1) or is it down (0). 52 | # TYPE ovs_up gauge 53 | ovs_up 1 54 | ``` 55 | 56 | ## Flags 57 | 58 | ```bash 59 | ./bin/ovs-exporter --help 60 | ``` 61 | 62 | ## Development Notes 63 | 64 | Run the following command to build `arm64`: 65 | 66 | ```bash 67 | make BUILD_OS="linux" BUILD_ARCH="arm64" 68 | ``` 69 | 70 | Next, package the binary: 71 | 72 | ```bash 73 | make BUILD_OS="linux" BUILD_ARCH="arm64" dist 74 | ``` 75 | 76 | After a successful release, upload packages to Github: 77 | 78 | ```bash 79 | owner=$(cat .git/config | egrep "^\s+url" | cut -d":" -f2 | cut -d"/" -f1) 80 | repo=$(cat .git/config | egrep "^\s+url" | cut -d":" -f2 | cut -d"/" -f2 | sed 's/.git$//') 81 | tag="v$(< VERSION)" 82 | github_api_token="PASTE_TOKEN_HERE" 83 | filename="./dist/${repo}-$(< VERSION).linux-amd64.tar.gz" 84 | upload-github-release-asset.sh github_api_token=${github_api_token} owner=${owner} repo=${repo} tag=${tag} filename=dist/ovs-exporter-$(< VERSION).linux-amd64.tar.gz 85 | upload-github-release-asset.sh github_api_token=${github_api_token} owner=${owner} repo=${repo} tag=${tag} filename=dist/ovs-exporter-$(< VERSION).linux-arm64.tar.gz 86 | ``` 87 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 4 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= 7 | github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 8 | github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= 9 | github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 10 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 11 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 12 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 13 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 14 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 15 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 16 | github.com/greenpau/ovsdb v1.0.4 h1:ekvfucZr5Dl/bYgcz6+nido4lSBDqG5kqLFUv55BqvQ= 17 | github.com/greenpau/ovsdb v1.0.4/go.mod h1:eZ72kooepm3wDa9o4YgmfEmbFCeibzSYrrZazwaopxo= 18 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 19 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 20 | github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= 21 | github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= 22 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 23 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 24 | github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= 25 | github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 26 | github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= 27 | github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= 28 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 29 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 30 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 32 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 33 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 34 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 35 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 36 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 37 | -------------------------------------------------------------------------------- /pkg/ovs_exporter/ovs_exporter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Paul Greenberg (greenpau@outlook.com) 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ovs_exporter 16 | 17 | import ( 18 | "io/ioutil" 19 | "net" 20 | "net/http" 21 | "strings" 22 | "testing" 23 | "time" 24 | 25 | "github.com/prometheus/client_golang/prometheus" 26 | "github.com/prometheus/client_golang/prometheus/promhttp" 27 | ) 28 | 29 | func TestNewExporter(t *testing.T) { 30 | logger, err := NewLogger("debug") 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | opts := Options{ 36 | Timeout: 2, 37 | Logger: logger, 38 | } 39 | 40 | exporter := NewExporter(opts) 41 | if err := exporter.Connect(); err != nil { 42 | t.Fatalf("expected no error, but got %q", err) 43 | } 44 | 45 | exporter.Client.System.RunDir = "/var/run/openvswitch" 46 | exporter.Client.Database.Vswitch.Name = "Open_vSwitch" 47 | exporter.Client.Database.Vswitch.Socket.Remote = "unix:/var/run/openvswitch/db.sock" 48 | exporter.Client.Database.Vswitch.File.Data.Path = "/etc/openvswitch/conf.db" 49 | exporter.Client.Database.Vswitch.File.Log.Path = "/var/log/openvswitch/ovsdb-server.log" 50 | exporter.Client.Database.Vswitch.File.Pid.Path = "/var/run/openvswitch/ovsdb-server.pid" 51 | exporter.Client.Database.Vswitch.File.SystemID.Path = "/etc/openvswitch/system-id.conf" 52 | 53 | exporter.Client.Service.Vswitchd.File.Log.Path = "/var/log/openvswitch/ovs-vswitchd.log" 54 | exporter.Client.Service.Vswitchd.File.Pid.Path = "/var/run/openvswitch/ovs-vswitchd.pid" 55 | 56 | exporter.Client.Service.OvnController.File.Log.Path = "/var/log/openvswitch/ovn-controller.log" 57 | exporter.Client.Service.OvnController.File.Pid.Path = "/var/run/openvswitch/ovn-controller.pid" 58 | 59 | exporter.SetPollInterval(int64(15)) 60 | prometheus.MustRegister(exporter) 61 | metricsPath := "/metrics" 62 | http.Handle(metricsPath, promhttp.Handler()) 63 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 64 | w.Write([]byte(` 65 | OVS Exporter 66 | 67 |

OVS Exporter

68 |

Metrics

69 | 70 | `)) 71 | }) 72 | 73 | go func() { 74 | http.ListenAndServe(":9475", nil) 75 | }() 76 | 77 | time.Sleep(1 * time.Second) 78 | 79 | tr := &http.Transport{ 80 | Dial: (&net.Dialer{ 81 | Timeout: 10 * time.Second, 82 | }).Dial, 83 | TLSHandshakeTimeout: 10 * time.Second, 84 | } 85 | 86 | httpClient := &http.Client{ 87 | Transport: tr, 88 | Timeout: time.Second * 30, 89 | } 90 | 91 | var req *http.Request 92 | req, err = http.NewRequest("GET", "http://127.0.0.1:9475/metrics", nil) 93 | if err != nil { 94 | t.Fatalf("%s", err) 95 | } 96 | 97 | res, err := httpClient.Do(req) 98 | if err != nil { 99 | if !strings.HasSuffix(err.Error(), "EOF") { 100 | t.Fatalf("%s", err) 101 | } 102 | } 103 | 104 | if res == nil { 105 | t.Fatalf("response: ") 106 | } 107 | defer res.Body.Close() 108 | 109 | body, err := ioutil.ReadAll(res.Body) 110 | if err != nil { 111 | t.Fatalf("non-EOF error: %s", err) 112 | } 113 | 114 | t.Logf("%s", string(body)) 115 | } 116 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test clean qtest deploy dist 2 | APP_VERSION:=$(shell cat VERSION | head -1) 3 | GIT_COMMIT:=$(shell git describe --dirty --always) 4 | GIT_BRANCH:=$(shell git rev-parse --abbrev-ref HEAD -- | head -1) 5 | BUILD_USER:=$(shell whoami) 6 | BUILD_DATE:=$(shell date +"%Y-%m-%d") 7 | BINARY:=ovs-exporter 8 | VERBOSE:=-v 9 | PROJECT=github.com/greenpau/ovs_exporter 10 | PKG_DIR=pkg/ovs_exporter 11 | BUILD_OS:=linux 12 | BUILD_ARCH:=amd64 13 | 14 | all: 15 | @echo "Version: $(APP_VERSION), Branch: $(GIT_BRANCH), Revision: $(GIT_COMMIT)" 16 | @echo "Build for $(BUILD_OS)-$(BUILD_ARCH) on $(BUILD_DATE) by $(BUILD_USER)" 17 | @rm -rf ./bin/$(BUILD_OS)-$(BUILD_ARCH) 18 | @mkdir -p bin/$(BUILD_OS)-$(BUILD_ARCH) 19 | @GOOS=$(BUILD_OS) GOARCH=$(BUILD_ARCH) CGO_ENABLED=0 go build -o ./bin/$(BUILD_OS)-$(BUILD_ARCH)/$(BINARY) $(VERBOSE) \ 20 | -ldflags="-w -s \ 21 | -X github.com/prometheus/common/version.Version=$(APP_VERSION) \ 22 | -X github.com/prometheus/common/version.Revision=$(GIT_COMMIT) \ 23 | -X github.com/prometheus/common/version.Branch=$(GIT_BRANCH) \ 24 | -X github.com/prometheus/common/version.BuildUser=$(BUILD_USER) \ 25 | -X github.com/prometheus/common/version.BuildDate=$(BUILD_DATE) \ 26 | -X $(PROJECT)/$(PKG_DIR).appName=$(BINARY) \ 27 | -X $(PROJECT)/$(PKG_DIR).appVersion=$(APP_VERSION) \ 28 | -X $(PROJECT)/$(PKG_DIR).gitBranch=$(GIT_BRANCH) \ 29 | -X $(PROJECT)/$(PKG_DIR).gitCommit=$(GIT_COMMIT) \ 30 | -X $(PROJECT)/$(PKG_DIR).buildUser=$(BUILD_USER) \ 31 | -X $(PROJECT)/$(PKG_DIR).buildDate=$(BUILD_DATE)" \ 32 | -gcflags="all=-trimpath=$(GOPATH)/src" \ 33 | -asmflags="all=-trimpath $(GOPATH)/src" \ 34 | ./cmd/ovs_exporter/*.go 35 | @echo "Done!" 36 | 37 | test: all 38 | @mkdir -p .coverage;\ 39 | rm -rf ./pkg/ovs_exporter/ovs_exporter.test;\ 40 | go test -c $(VERBOSE) -coverprofile=.coverage/coverage.out ./pkg/ovs_exporter/*.go;\ 41 | mv ./ovs_exporter.test ./pkg/ovs_exporter/ovs_exporter.test;\ 42 | chmod +x ./pkg/ovs_exporter/ovs_exporter.test;\ 43 | sudo ./pkg/ovs_exporter/ovs_exporter.test -test.v -test.testlogfile ./.coverage/test.log -test.coverprofile ./.coverage/coverage.out 44 | @echo "PASS: core tests" 45 | @echo "OK: all tests passed!" 46 | 47 | coverage: 48 | @go tool cover -html=.coverage/coverage.out -o .coverage/coverage.html 49 | @go tool cover -func=.coverage/coverage.out 50 | 51 | clean: 52 | @rm -rf bin/ 53 | @rm -rf dist/ 54 | @echo "OK: clean up completed" 55 | 56 | dep: 57 | @echo "Making dependencies check ..." 58 | @go get -u golang.org/x/lint/golint 59 | @go get -u golang.org/x/tools/cmd/godoc 60 | @go get -u github.com/kyoh86/richgo 61 | @go get -u github.com/greenpau/versioned/cmd/versioned 62 | @go get -u github.com/google/addlicense 63 | 64 | deploy: 65 | @sudo rm -rf /usr/sbin/$(BINARY) 66 | @sudo cp ./bin/$(BUILD_OS)-$(BUILD_ARCH)/$(BINARY) /usr/sbin/$(BINARY) 67 | @sudo usermod -a -G openvswitch ovs_exporter 68 | @sudo chmod g+w /var/run/openvswitch/db.sock 69 | @sudo setcap cap_sys_admin,cap_sys_nice,cap_dac_override+ep /usr/sbin/$(BINARY) 70 | 71 | qtest: 72 | @./bin/$(BUILD_OS)-$(BUILD_ARCH)/$(BINARY) -version 73 | @sudo ./bin/$(BUILD_OS)-$(BUILD_ARCH)/$(BINARY) -web.listen-address 0.0.0.0:5000 -log.level debug -ovs.poll-interval 5 74 | 75 | dist: all 76 | @mkdir -p ./dist 77 | @rm -rf ./dist/$(BINARY)-$(APP_VERSION).$(BUILD_OS)-$(BUILD_ARCH) 78 | @mkdir -p ./dist/$(BINARY)-$(APP_VERSION).$(BUILD_OS)-$(BUILD_ARCH) 79 | @cp ./bin/$(BUILD_OS)-$(BUILD_ARCH)/$(BINARY) ./dist/$(BINARY)-$(APP_VERSION).$(BUILD_OS)-$(BUILD_ARCH)/ 80 | @cp ./README.md ./dist/$(BINARY)-$(APP_VERSION).$(BUILD_OS)-$(BUILD_ARCH)/ 81 | @cp LICENSE ./dist/$(BINARY)-$(APP_VERSION).$(BUILD_OS)-$(BUILD_ARCH)/ 82 | @cp assets/systemd/add_service.sh ./dist/$(BINARY)-$(APP_VERSION).$(BUILD_OS)-$(BUILD_ARCH)/install.sh 83 | @chmod +x ./dist/$(BINARY)-$(APP_VERSION).$(BUILD_OS)-$(BUILD_ARCH)/*.sh 84 | @cd ./dist/ && tar -cvzf ./$(BINARY)-$(APP_VERSION).$(BUILD_OS)-$(BUILD_ARCH).tar.gz ./$(BINARY)-$(APP_VERSION).$(BUILD_OS)-$(BUILD_ARCH) 85 | 86 | license: 87 | @addlicense -c "Paul Greenberg greenpau@outlook.com" -y 2020 pkg/*/*.go 88 | 89 | release: license 90 | @echo "Making release" 91 | @go mod tidy 92 | @go mod verify 93 | @if [ $(GIT_BRANCH) != "main" ]; then echo "cannot release to non-main branch $(GIT_BRANCH)" && false; fi 94 | @git diff-index --quiet HEAD -- || ( echo "git directory is dirty, commit changes first" && git status && false ) 95 | @versioned -patch 96 | @echo "Patched version" 97 | @git add VERSION 98 | @git commit -m "released v`cat VERSION | head -1`" 99 | @git tag -a v`cat VERSION | head -1` -m "v`cat VERSION | head -1`" 100 | @git push 101 | @git push --tags 102 | @@echo "If necessary, run the following commands:" 103 | @echo " git push --delete origin v$(APP_VERSION)" 104 | @echo " git tag --delete v$(APP_VERSION)" 105 | -------------------------------------------------------------------------------- /cmd/ovs_exporter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/go-kit/log/level" 10 | ovs "github.com/greenpau/ovs_exporter/pkg/ovs_exporter" 11 | "github.com/prometheus/client_golang/prometheus" 12 | "github.com/prometheus/client_golang/prometheus/promhttp" 13 | ) 14 | 15 | func main() { 16 | var listenAddress string 17 | var metricsPath string 18 | var pollTimeout int 19 | var pollInterval int 20 | var isShowVersion bool 21 | var logLevel string 22 | var systemRunDir string 23 | var databaseVswitchName string 24 | var databaseVswitchSocketRemote string 25 | var databaseVswitchFileDataPath string 26 | var databaseVswitchFileLogPath string 27 | var databaseVswitchFilePidPath string 28 | var databaseVswitchFileSystemIDPath string 29 | var serviceVswitchdFileLogPath string 30 | var serviceVswitchdFilePidPath string 31 | var serviceOvnControllerFileLogPath string 32 | var serviceOvnControllerFilePidPath string 33 | 34 | flag.StringVar(&listenAddress, "web.listen-address", ":9475", "Address to listen on for web interface and telemetry.") 35 | flag.StringVar(&metricsPath, "web.telemetry-path", "/metrics", "Path under which to expose metrics.") 36 | flag.IntVar(&pollTimeout, "ovs.timeout", 2, "Timeout on JSON-RPC requests to OVS.") 37 | flag.IntVar(&pollInterval, "ovs.poll-interval", 15, "The minimum interval (in seconds) between collections from OVS server.") 38 | flag.BoolVar(&isShowVersion, "version", false, "version information") 39 | flag.StringVar(&logLevel, "log.level", "info", "logging severity level") 40 | 41 | flag.StringVar(&systemRunDir, "system.run.dir", "/var/run/openvswitch", "OVS default run directory.") 42 | 43 | flag.StringVar(&databaseVswitchName, "database.vswitch.name", "Open_vSwitch", "The name of OVS db.") 44 | flag.StringVar(&databaseVswitchSocketRemote, "database.vswitch.socket.remote", "unix:/var/run/openvswitch/db.sock", "JSON-RPC unix socket to OVS db.") 45 | flag.StringVar(&databaseVswitchFileDataPath, "database.vswitch.file.data.path", "/etc/openvswitch/conf.db", "OVS db file.") 46 | flag.StringVar(&databaseVswitchFileLogPath, "database.vswitch.file.log.path", "/var/log/openvswitch/ovsdb-server.log", "OVS db log file.") 47 | flag.StringVar(&databaseVswitchFilePidPath, "database.vswitch.file.pid.path", "/var/run/openvswitch/ovsdb-server.pid", "OVS db process id file.") 48 | flag.StringVar(&databaseVswitchFileSystemIDPath, "database.vswitch.file.system.id.path", "/etc/openvswitch/system-id.conf", "OVS system id file.") 49 | 50 | flag.StringVar(&serviceVswitchdFileLogPath, "service.vswitchd.file.log.path", "/var/log/openvswitch/ovs-vswitchd.log", "OVS vswitchd daemon log file.") 51 | flag.StringVar(&serviceVswitchdFilePidPath, "service.vswitchd.file.pid.path", "/var/run/openvswitch/ovs-vswitchd.pid", "OVS vswitchd daemon process id file.") 52 | 53 | flag.StringVar(&serviceOvnControllerFileLogPath, "service.ovncontroller.file.log.path", "/var/log/openvswitch/ovn-controller.log", "OVN controller daemon log file.") 54 | flag.StringVar(&serviceOvnControllerFilePidPath, "service.ovncontroller.file.pid.path", "/var/run/openvswitch/ovn-controller.pid", "OVN controller daemon process id file.") 55 | 56 | var usageHelp = func() { 57 | fmt.Fprintf(os.Stderr, "\n%s - Prometheus Exporter for Open Virtual Switch (OVS)\n\n", ovs.GetExporterName()) 58 | fmt.Fprintf(os.Stderr, "Usage: %s [arguments]\n\n", ovs.GetExporterName()) 59 | flag.PrintDefaults() 60 | fmt.Fprintf(os.Stderr, "\nDocumentation: https://github.com/greenpau/ovs_exporter/\n\n") 61 | } 62 | flag.Usage = usageHelp 63 | flag.Parse() 64 | 65 | if isShowVersion { 66 | fmt.Fprintf(os.Stdout, "%s %s", ovs.GetExporterName(), ovs.GetVersion()) 67 | if ovs.GetRevision() != "" { 68 | fmt.Fprintf(os.Stdout, ", commit: %s\n", ovs.GetRevision()) 69 | } else { 70 | fmt.Fprint(os.Stdout, "\n") 71 | } 72 | os.Exit(0) 73 | } 74 | 75 | logger, err := ovs.NewLogger(logLevel) 76 | if err != nil { 77 | fmt.Fprintf(os.Stderr, "failed initializing logger: %v", err) 78 | os.Exit(1) 79 | } 80 | 81 | level.Info(logger).Log( 82 | "msg", "Starting exporter", 83 | "exporter", ovs.GetExporterName(), 84 | "version", ovs.GetVersionInfo(), 85 | "build_context", ovs.GetVersionBuildContext(), 86 | ) 87 | 88 | opts := ovs.Options{ 89 | Timeout: pollTimeout, 90 | Logger: logger, 91 | } 92 | 93 | exporter := ovs.NewExporter(opts) 94 | 95 | exporter.Client.System.RunDir = systemRunDir 96 | 97 | exporter.Client.Database.Vswitch.Name = databaseVswitchName 98 | exporter.Client.Database.Vswitch.Socket.Remote = databaseVswitchSocketRemote 99 | exporter.Client.Database.Vswitch.File.Data.Path = databaseVswitchFileDataPath 100 | exporter.Client.Database.Vswitch.File.Log.Path = databaseVswitchFileLogPath 101 | exporter.Client.Database.Vswitch.File.Pid.Path = databaseVswitchFilePidPath 102 | exporter.Client.Database.Vswitch.File.SystemID.Path = databaseVswitchFileSystemIDPath 103 | 104 | exporter.Client.Service.Vswitchd.File.Log.Path = serviceVswitchdFileLogPath 105 | exporter.Client.Service.Vswitchd.File.Pid.Path = serviceVswitchdFilePidPath 106 | 107 | exporter.Client.Service.OvnController.File.Log.Path = serviceOvnControllerFileLogPath 108 | exporter.Client.Service.OvnController.File.Pid.Path = serviceOvnControllerFilePidPath 109 | if err := exporter.Connect(); err != nil { 110 | level.Error(logger).Log( 111 | "msg", "failed to init properly", 112 | "error", err.Error(), 113 | ) 114 | os.Exit(1) 115 | } 116 | 117 | level.Info(logger).Log("ovs_system_id", exporter.Client.System.ID) 118 | 119 | exporter.SetPollInterval(int64(pollInterval)) 120 | prometheus.MustRegister(exporter) 121 | 122 | http.Handle(metricsPath, promhttp.Handler()) 123 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 124 | w.Write([]byte(` 125 | OVS Exporter 126 | 127 |

OVS Exporter

128 |

Metrics

129 | 130 | `)) 131 | }) 132 | 133 | level.Info(logger).Log("listen_on ", listenAddress) 134 | if err := http.ListenAndServe(listenAddress, nil); err != nil { 135 | level.Error(logger).Log( 136 | "msg", "listener failed", 137 | "error", err.Error(), 138 | ) 139 | os.Exit(1) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Paul Greenberg 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /pkg/ovs_exporter/ovs_exporter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Paul Greenberg (greenpau@outlook.com) 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ovs_exporter 16 | 17 | import ( 18 | "fmt" 19 | _ "net/http/pprof" 20 | "sync" 21 | "sync/atomic" 22 | "time" 23 | 24 | "github.com/go-kit/log" 25 | "github.com/go-kit/log/level" 26 | "github.com/greenpau/ovsdb" 27 | "github.com/prometheus/client_golang/prometheus" 28 | "github.com/prometheus/common/promlog" 29 | "github.com/prometheus/common/version" 30 | ) 31 | 32 | const ( 33 | namespace = "ovs" 34 | ) 35 | 36 | var ( 37 | appName = "ovs-exporter" 38 | appVersion = "[untracked]" 39 | gitBranch string 40 | gitCommit string 41 | buildUser string // whoami 42 | buildDate string // date -u 43 | ) 44 | 45 | var ( 46 | up = prometheus.NewDesc( 47 | prometheus.BuildFQName(namespace, "", "up"), 48 | "Is OVN stack up (1) or is it down (0).", 49 | nil, nil, 50 | ) 51 | info = prometheus.NewDesc( 52 | prometheus.BuildFQName(namespace, "", "info"), 53 | "This metric provides basic information about OVN stack. It is always set to 1.", 54 | []string{ 55 | "system_id", 56 | "rundir", 57 | "hostname", 58 | "system_type", 59 | "system_version", 60 | "ovs_version", 61 | "db_version", 62 | }, nil, 63 | ) 64 | requestErrors = prometheus.NewDesc( 65 | prometheus.BuildFQName(namespace, "", "failed_req_count"), 66 | "The number of failed requests to OVN stack.", 67 | []string{"system_id"}, nil, 68 | ) 69 | nextPoll = prometheus.NewDesc( 70 | prometheus.BuildFQName(namespace, "", "next_poll"), 71 | "The timestamp of the next potential poll of OVN stack.", 72 | []string{"system_id"}, nil, 73 | ) 74 | pid = prometheus.NewDesc( 75 | prometheus.BuildFQName(namespace, "", "pid"), 76 | "The process ID of a running OVN component. If the component is not running, then the ID is 0.", 77 | []string{"system_id", "component", "user", "group"}, nil, 78 | ) 79 | logFileSize = prometheus.NewDesc( 80 | prometheus.BuildFQName(namespace, "", "log_file_size"), 81 | "The size of a log file associated with an OVN component.", 82 | []string{"system_id", "component", "filename"}, nil, 83 | ) 84 | logEventStat = prometheus.NewDesc( 85 | prometheus.BuildFQName(namespace, "", "log_event_count"), 86 | "The number of recorded log meessage associated with an OVN component by log severity level and source.", 87 | []string{"system_id", "component", "severity", "source"}, nil, 88 | ) 89 | dbFileSize = prometheus.NewDesc( 90 | prometheus.BuildFQName(namespace, "", "db_file_size"), 91 | "The size of a database file associated with an OVN component.", 92 | []string{"system_id", "component", "filename"}, nil, 93 | ) 94 | networkPortUp = prometheus.NewDesc( 95 | prometheus.BuildFQName(namespace, "", "network_port"), 96 | "The TCP port used for database connection. If the value is 0, then the port is not in use.", 97 | []string{"system_id", "component", "usage"}, nil, 98 | ) 99 | // OVS Coverage and Memory 100 | covAvg = prometheus.NewDesc( 101 | prometheus.BuildFQName(namespace, "", "coverage_avg"), 102 | "The average rate of the number of times particular events occur during a OVSDB daemon's runtime.", 103 | []string{"system_id", "component", "event", "interval"}, nil, 104 | ) 105 | covTotal = prometheus.NewDesc( 106 | prometheus.BuildFQName(namespace, "", "coverage_total"), 107 | "The total number of times particular events occur during a OVSDB daemon's runtime.", 108 | []string{"system_id", "component", "event"}, nil, 109 | ) 110 | memUsage = prometheus.NewDesc( 111 | prometheus.BuildFQName(namespace, "", "memory_usage"), 112 | "The memory usage.", 113 | []string{"system_id", "component", "facility"}, nil, 114 | ) 115 | // OVS Datapath 116 | dpInterface = prometheus.NewDesc( 117 | prometheus.BuildFQName(namespace, "", "dp_if"), 118 | "Represents an existing datapath interface. This metrics is always 1.", 119 | []string{"system_id", "datapath", "bridge", "name", "ofport", "index", "port_type"}, nil, 120 | ) 121 | dpBridgeInterfaceTotal = prometheus.NewDesc( 122 | prometheus.BuildFQName(namespace, "", "dp_br_if_total"), 123 | "The total number of interfaces attached to a bridge.", 124 | []string{"system_id", "datapath", "bridge"}, nil, 125 | ) 126 | dpFlowsTotal = prometheus.NewDesc( 127 | prometheus.BuildFQName(namespace, "", "dp_flows"), 128 | "The number of flows in a datapath.", 129 | []string{"system_id", "datapath"}, nil, 130 | ) 131 | // OVS Datapath: Lookups 132 | dpLookupsHit = prometheus.NewDesc( 133 | prometheus.BuildFQName(namespace, "", "dp_lookups_hit"), 134 | "The number of incoming packets in a datapath matching existing flows in the datapath.", 135 | []string{"system_id", "datapath"}, nil, 136 | ) 137 | dpLookupsMissed = prometheus.NewDesc( 138 | prometheus.BuildFQName(namespace, "", "dp_lookups_missed"), 139 | "The number of incoming packets in a datapath not matching any existing flow in the datapath.", 140 | []string{"system_id", "datapath"}, nil, 141 | ) 142 | dpLookupsLost = prometheus.NewDesc( 143 | prometheus.BuildFQName(namespace, "", "dp_lookups_lost"), 144 | "Returns the number of incoming packets in a datapath destined for userspace process but subsequently dropped before reaching userspace.", 145 | []string{"system_id", "datapath"}, nil, 146 | ) 147 | // OVS Datapath: Masks 148 | dpMasksHit = prometheus.NewDesc( 149 | prometheus.BuildFQName(namespace, "", "dp_masks_hit"), 150 | "The total number of masks visited for matching incoming packets.", 151 | []string{"system_id", "datapath"}, nil, 152 | ) 153 | dpMasksTotal = prometheus.NewDesc( 154 | prometheus.BuildFQName(namespace, "", "dp_masks_total"), 155 | "The number of masks in a datapath.", 156 | []string{"system_id", "datapath"}, nil, 157 | ) 158 | dpMasksHitRatio = prometheus.NewDesc( 159 | prometheus.BuildFQName(namespace, "", "dp_masks_hit_ratio"), 160 | "The average number of masks visited per packet. It is the ration between hit and total number of packets processed by a datapath.", 161 | []string{"system_id", "datapath"}, nil, 162 | ) 163 | // OVS Interface 164 | // Reference: http://www.openvswitch.org/support/dist-docs/ovs-vswitchd.conf.db.5.html 165 | interfaceMain = prometheus.NewDesc( 166 | prometheus.BuildFQName(namespace, "", "interface"), 167 | "Represents OVS interface. This is the primary metric for all other interface metrics. This metrics is always 1.", 168 | []string{"system_id", "uuid", "name"}, nil, 169 | ) 170 | interfaceAdminState = prometheus.NewDesc( 171 | prometheus.BuildFQName(namespace, "", "interface_admin_state"), 172 | "The administrative state of the physical network link of OVS interface. The values are: down(0), up(1), other(2).", 173 | []string{"system_id", "uuid"}, nil, 174 | ) 175 | interfaceLinkState = prometheus.NewDesc( 176 | prometheus.BuildFQName(namespace, "", "interface_link_state"), 177 | "The observed state of the physical network link of OVS interface. The values are: down(0), up(1), other(2).", 178 | []string{"system_id", "uuid"}, nil, 179 | ) 180 | interfaceIngressPolicingBurst = prometheus.NewDesc( 181 | prometheus.BuildFQName(namespace, "", "interface_ingress_policing_burst"), 182 | "Maximum burst size for data received on OVS interface, in kb. The default burst size if set to 0 is 8000 kbit.", 183 | []string{"system_id", "uuid"}, nil, 184 | ) 185 | interfaceIngressPolicingRate = prometheus.NewDesc( 186 | prometheus.BuildFQName(namespace, "", "interface_ingress_policing_rate"), 187 | "Maximum rate for data received on OVS interface, in kbps. If the value is 0, then policing is disabled.", 188 | []string{"system_id", "uuid"}, nil, 189 | ) 190 | interfaceMacInUse = prometheus.NewDesc( 191 | prometheus.BuildFQName(namespace, "", "interface_mac_in_use"), 192 | "The MAC address in use by OVS interface.", 193 | []string{"system_id", "uuid", "mac_address"}, nil, 194 | ) 195 | interfaceMtu = prometheus.NewDesc( 196 | prometheus.BuildFQName(namespace, "", "interface_mtu"), 197 | "The currently configured MTU for OVS interface.", 198 | []string{"system_id", "uuid"}, nil, 199 | ) 200 | interfaceDuplex = prometheus.NewDesc( 201 | prometheus.BuildFQName(namespace, "", "interface_duplex"), 202 | "The duplex mode of the physical network link of OVS interface. The values are: other(0), half(1), full(2).", 203 | []string{"system_id", "uuid"}, nil, 204 | ) 205 | interfaceOfPort = prometheus.NewDesc( 206 | prometheus.BuildFQName(namespace, "", "interface_of_port"), 207 | "Represents the OpenFlow port ID associated with OVS interface.", 208 | []string{"system_id", "uuid"}, nil, 209 | ) 210 | interfaceIfIndex = prometheus.NewDesc( 211 | prometheus.BuildFQName(namespace, "", "interface_if_index"), 212 | "Represents the interface index associated with OVS interface.", 213 | []string{"system_id", "uuid"}, nil, 214 | ) 215 | interfaceLocalIndex = prometheus.NewDesc( 216 | prometheus.BuildFQName(namespace, "", "interface_local_index"), 217 | "Represents the local index associated with OVS interface.", 218 | []string{"system_id", "uuid"}, nil, 219 | ) 220 | // OVS Interface Statistics: Receive errors 221 | // See http://www.openvswitch.org/support/dist-docs/ovs-vswitchd.conf.db.5.html 222 | interfaceStatRxCrcError = prometheus.NewDesc( 223 | prometheus.BuildFQName(namespace, "", "interface_rx_crc_err"), 224 | "Represents the number of CRC errors for the packets received by OVS interface.", 225 | []string{"system_id", "uuid"}, nil, 226 | ) 227 | interfaceStatRxDropped = prometheus.NewDesc( 228 | prometheus.BuildFQName(namespace, "", "interface_rx_dropped"), 229 | "Represents the number of input packets dropped by OVS interface.", 230 | []string{"system_id", "uuid"}, nil, 231 | ) 232 | interfaceStatRxFrameError = prometheus.NewDesc( 233 | prometheus.BuildFQName(namespace, "", "interface_rx_frame_err"), 234 | "Represents the number of frame alignment errors on the packets received by OVS interface.", 235 | []string{"system_id", "uuid"}, nil, 236 | ) 237 | interfaceStatRxOverrunError = prometheus.NewDesc( 238 | prometheus.BuildFQName(namespace, "", "interface_rx_over_err"), 239 | "Represents the number of packets with RX overrun received by OVS interface.", 240 | []string{"system_id", "uuid"}, nil, 241 | ) 242 | interfaceStatRxErrorsTotal = prometheus.NewDesc( 243 | prometheus.BuildFQName(namespace, "", "interface_rx_errors"), 244 | "Represents the total number of packets with errors received by OVS interface.", 245 | []string{"system_id", "uuid"}, nil, 246 | ) 247 | interfaceStatRxMissedErrors = prometheus.NewDesc( 248 | prometheus.BuildFQName(namespace, "", "interface_rx_missed_errors"), 249 | "Represents the number of missed packets received by OVS interface.", 250 | []string{"system_id", "uuid"}, nil, 251 | ) 252 | // OVS Interface Statistics: Successful transmit and receive counters 253 | interfaceStatRxPackets = prometheus.NewDesc( 254 | prometheus.BuildFQName(namespace, "", "interface_rx_packets"), 255 | "Represents the number of received packets by OVS interface.", 256 | []string{"system_id", "uuid"}, nil, 257 | ) 258 | interfaceStatRxBytes = prometheus.NewDesc( 259 | prometheus.BuildFQName(namespace, "", "interface_rx_bytes"), 260 | "Represents the number of received bytes by OVS interface.", 261 | []string{"system_id", "uuid"}, nil, 262 | ) 263 | interfaceStatTxPackets = prometheus.NewDesc( 264 | prometheus.BuildFQName(namespace, "", "interface_tx_packets"), 265 | "Represents the number of transmitted packets by OVS interface.", 266 | []string{"system_id", "uuid"}, nil, 267 | ) 268 | interfaceStatTxBytes = prometheus.NewDesc( 269 | prometheus.BuildFQName(namespace, "", "interface_tx_bytes"), 270 | "Represents the number of transmitted bytes by OVS interface.", 271 | []string{"system_id", "uuid"}, nil, 272 | ) 273 | // OVS Interface Statistics: Transmit errors 274 | interfaceStatTxDropped = prometheus.NewDesc( 275 | prometheus.BuildFQName(namespace, "", "interface_tx_dropped"), 276 | "Represents the number of output packets dropped by OVS interface.", 277 | []string{"system_id", "uuid"}, nil, 278 | ) 279 | interfaceStatTxErrorsTotal = prometheus.NewDesc( 280 | prometheus.BuildFQName(namespace, "", "interface_tx_errors"), 281 | "Represents the total number of transmit errors by OVS interface.", 282 | []string{"system_id", "uuid"}, nil, 283 | ) 284 | interfaceStatCollisions = prometheus.NewDesc( 285 | prometheus.BuildFQName(namespace, "", "interface_collisions"), 286 | "Represents the number of collisions on OVS interface.", 287 | []string{"system_id", "uuid"}, nil, 288 | ) 289 | // OVS Link attributes, e.g. speed, resets, etc. 290 | interfaceLinkResets = prometheus.NewDesc( 291 | prometheus.BuildFQName(namespace, "", "interface_link_resets"), 292 | "The number of times Open vSwitch has observed the link_state of OVS interface change.", 293 | []string{"system_id", "uuid"}, nil, 294 | ) 295 | interfaceLinkSpeed = prometheus.NewDesc( 296 | prometheus.BuildFQName(namespace, "", "interface_link_speed"), 297 | "The negotiated speed of the physical network link of OVS interface.", 298 | []string{"system_id", "uuid"}, nil, 299 | ) 300 | // Interface Status, Options, and External IDs Key-Value Pairs 301 | interfaceStatusKeyValuePair = prometheus.NewDesc( 302 | prometheus.BuildFQName(namespace, "", "interface_status"), 303 | "Key-value pair that report port status of OVS interface.", 304 | []string{"system_id", "uuid", "key", "value"}, nil, 305 | ) 306 | interfaceOptionsKeyValuePair = prometheus.NewDesc( 307 | prometheus.BuildFQName(namespace, "", "interface_options"), 308 | "Key-value pair that report options of OVS interface.", 309 | []string{"system_id", "uuid", "key", "value"}, nil, 310 | ) 311 | interfaceExternalIdKeyValuePair = prometheus.NewDesc( 312 | prometheus.BuildFQName(namespace, "", "interface_external_ids"), 313 | "Key-value pair that report external IDs of OVS interface.", 314 | []string{"system_id", "uuid", "key", "value"}, nil, 315 | ) 316 | interfaceStateMulticastPackets = prometheus.NewDesc( 317 | prometheus.BuildFQName(namespace, "", "interface_rx_multicast_packets"), 318 | "Represents the number of received multicast packets by OVS interface.", 319 | []string{"system_id", "uuid"}, nil, 320 | ) 321 | ) 322 | 323 | // Exporter collects OVN data from the given server and exports them using 324 | // the prometheus metrics package. 325 | type Exporter struct { 326 | sync.RWMutex 327 | Client *ovsdb.OvsClient 328 | timeout int 329 | pollInterval int64 330 | errors int64 331 | errorsLocker sync.RWMutex 332 | nextCollectionTicker int64 333 | metrics []prometheus.Metric 334 | logger log.Logger 335 | } 336 | 337 | type Options struct { 338 | Timeout int 339 | Logger log.Logger 340 | } 341 | 342 | // NewLogger returns an instance of logger. 343 | func NewLogger(logLevel string) (log.Logger, error) { 344 | allowedLogLevel := &promlog.AllowedLevel{} 345 | if err := allowedLogLevel.Set(logLevel); err != nil { 346 | return nil, err 347 | } 348 | promlogConfig := &promlog.Config{ 349 | Level: allowedLogLevel, 350 | } 351 | logger := promlog.New(promlogConfig) 352 | return logger, nil 353 | } 354 | 355 | // NewExporter returns an initialized Exporter. 356 | func NewExporter(opts Options) *Exporter { 357 | version.Version = appVersion 358 | version.Revision = gitCommit 359 | version.Branch = gitBranch 360 | version.BuildUser = buildUser 361 | version.BuildDate = buildDate 362 | e := Exporter{ 363 | timeout: opts.Timeout, 364 | } 365 | client := ovsdb.NewOvsClient() 366 | client.Timeout = opts.Timeout 367 | e.Client = client 368 | e.logger = opts.Logger 369 | return &e 370 | } 371 | 372 | func (e *Exporter) Connect() error { 373 | e.Client.GetSystemID() 374 | level.Debug(e.logger).Log( 375 | "msg", "NewExporter() calls Connect()", 376 | "system_id", e.Client.System.ID, 377 | ) 378 | 379 | if err := e.Client.Connect(); err != nil { 380 | return err 381 | } 382 | 383 | level.Debug(e.logger).Log( 384 | "msg", "NewExporter() calls GetSystemInfo()", 385 | "system_id", e.Client.System.ID, 386 | ) 387 | 388 | if err := e.Client.GetSystemInfo(); err != nil { 389 | return err 390 | } 391 | 392 | level.Debug(e.logger).Log( 393 | "msg", "NewExporter() initialized successfully", 394 | "system_id", e.Client.System.ID, 395 | ) 396 | return nil 397 | } 398 | 399 | // Describe describes all the metrics ever exported by the OVN exporter. It 400 | // implements prometheus.Collector. 401 | func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { 402 | ch <- up 403 | ch <- info 404 | ch <- requestErrors 405 | ch <- nextPoll 406 | ch <- pid 407 | ch <- logFileSize 408 | ch <- dbFileSize 409 | ch <- logEventStat 410 | ch <- networkPortUp 411 | ch <- covAvg 412 | ch <- covTotal 413 | ch <- memUsage 414 | ch <- dpInterface 415 | ch <- dpBridgeInterfaceTotal 416 | ch <- dpLookupsHit 417 | ch <- dpFlowsTotal 418 | ch <- dpLookupsMissed 419 | ch <- dpMasksHit 420 | ch <- dpMasksTotal 421 | ch <- dpMasksHitRatio 422 | ch <- dpLookupsLost 423 | ch <- interfaceMain 424 | ch <- interfaceAdminState 425 | ch <- interfaceLinkState 426 | ch <- interfaceIngressPolicingBurst 427 | ch <- interfaceIngressPolicingRate 428 | ch <- interfaceMacInUse 429 | ch <- interfaceMtu 430 | ch <- interfaceDuplex 431 | ch <- interfaceOfPort 432 | ch <- interfaceIfIndex 433 | ch <- interfaceLocalIndex 434 | ch <- interfaceStatRxCrcError 435 | ch <- interfaceStatRxDropped 436 | ch <- interfaceStatRxFrameError 437 | ch <- interfaceStatRxOverrunError 438 | ch <- interfaceStatRxErrorsTotal 439 | ch <- interfaceStatRxMissedErrors 440 | ch <- interfaceStatRxPackets 441 | ch <- interfaceStatRxBytes 442 | ch <- interfaceStatTxPackets 443 | ch <- interfaceStatTxBytes 444 | ch <- interfaceStatTxDropped 445 | ch <- interfaceStatTxErrorsTotal 446 | ch <- interfaceStatCollisions 447 | ch <- interfaceLinkResets 448 | ch <- interfaceLinkSpeed 449 | ch <- interfaceStatusKeyValuePair 450 | ch <- interfaceOptionsKeyValuePair 451 | ch <- interfaceExternalIdKeyValuePair 452 | ch <- interfaceStateMulticastPackets 453 | } 454 | 455 | // IncrementErrorCounter increases the counter of failed queries 456 | // to OVN server. 457 | func (e *Exporter) IncrementErrorCounter() { 458 | e.errorsLocker.Lock() 459 | defer e.errorsLocker.Unlock() 460 | atomic.AddInt64(&e.errors, 1) 461 | } 462 | 463 | // Collect implements prometheus.Collector. 464 | func (e *Exporter) Collect(ch chan<- prometheus.Metric) { 465 | e.GatherMetrics() 466 | 467 | level.Debug(e.logger).Log( 468 | "msg", "Collect() calls RLock()", 469 | "system_id", e.Client.System.ID, 470 | ) 471 | 472 | e.RLock() 473 | defer e.RUnlock() 474 | if len(e.metrics) == 0 { 475 | level.Debug(e.logger).Log( 476 | "msg", "Collect() no metrics found", 477 | "system_id", e.Client.System.ID, 478 | ) 479 | 480 | ch <- prometheus.MustNewConstMetric( 481 | up, 482 | prometheus.GaugeValue, 483 | 0, 484 | ) 485 | ch <- prometheus.MustNewConstMetric( 486 | info, 487 | prometheus.GaugeValue, 488 | 1, 489 | e.Client.System.ID, e.Client.System.RunDir, e.Client.System.Hostname, 490 | e.Client.System.Type, e.Client.System.Version, 491 | e.Client.Database.Vswitch.Version, e.Client.Database.Vswitch.Schema.Version, 492 | ) 493 | ch <- prometheus.MustNewConstMetric( 494 | requestErrors, 495 | prometheus.CounterValue, 496 | float64(e.errors), 497 | e.Client.System.ID, 498 | ) 499 | ch <- prometheus.MustNewConstMetric( 500 | nextPoll, 501 | prometheus.CounterValue, 502 | float64(e.nextCollectionTicker), 503 | e.Client.System.ID, 504 | ) 505 | return 506 | } 507 | 508 | level.Debug(e.logger).Log( 509 | "msg", "Collect() sends metrics to a shared channel", 510 | "system_id", e.Client.System.ID, 511 | "metric_count", len(e.metrics), 512 | ) 513 | 514 | for _, m := range e.metrics { 515 | ch <- m 516 | } 517 | } 518 | 519 | // GatherMetrics collect data from OVN server and stores them 520 | // as Prometheus metrics. 521 | func (e *Exporter) GatherMetrics() { 522 | level.Debug(e.logger).Log( 523 | "msg", "GatherMetrics() called", 524 | "system_id", e.Client.System.ID, 525 | ) 526 | 527 | if time.Now().Unix() < e.nextCollectionTicker { 528 | return 529 | } 530 | e.Lock() 531 | level.Debug(e.logger).Log( 532 | "msg", "GatherMetrics() locked", 533 | "system_id", e.Client.System.ID, 534 | ) 535 | defer e.Unlock() 536 | if len(e.metrics) > 0 { 537 | e.metrics = e.metrics[:0] 538 | level.Debug(e.logger).Log( 539 | "msg", "GatherMetrics() cleared metrics", 540 | "system_id", e.Client.System.ID, 541 | ) 542 | } 543 | upValue := 1 544 | 545 | var err error 546 | 547 | err = e.Client.GetSystemInfo() 548 | if err != nil { 549 | level.Error(e.logger).Log( 550 | "msg", "GetSystemInfo() failed", 551 | "vswitch_name", e.Client.Database.Vswitch.Name, 552 | "system_id", e.Client.System.ID, 553 | "error", err.Error(), 554 | ) 555 | e.IncrementErrorCounter() 556 | upValue = 0 557 | } else { 558 | level.Debug(e.logger).Log( 559 | "msg", "GetSystemInfo() successful", 560 | "vswitch_name", e.Client.Database.Vswitch.Name, 561 | "system_id", e.Client.System.ID, 562 | ) 563 | } 564 | 565 | components := []string{ 566 | "ovsdb-server", 567 | "ovs-vswitchd", 568 | } 569 | for _, component := range components { 570 | p, err := e.Client.GetProcessInfo(component) 571 | level.Debug(e.logger).Log( 572 | "msg", "GatherMetrics() calls GetProcessInfo()", 573 | "component", component, 574 | "system_id", e.Client.System.ID, 575 | ) 576 | 577 | if err != nil { 578 | level.Error(e.logger).Log( 579 | "msg", "GetProcessInfo() failed", 580 | "component", component, 581 | "system_id", e.Client.System.ID, 582 | "error", err.Error(), 583 | ) 584 | e.IncrementErrorCounter() 585 | upValue = 0 586 | } 587 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 588 | pid, 589 | prometheus.GaugeValue, 590 | float64(p.ID), 591 | e.Client.System.ID, 592 | component, 593 | p.User, 594 | p.Group, 595 | )) 596 | level.Debug(e.logger).Log( 597 | "msg", "GatherMetrics() completed GetProcessInfo()", 598 | "component", component, 599 | "system_id", e.Client.System.ID, 600 | ) 601 | } 602 | 603 | components = []string{ 604 | "ovsdb-server", 605 | "ovs-vswitchd", 606 | } 607 | for _, component := range components { 608 | level.Debug(e.logger).Log( 609 | "msg", "GatherMetrics() calls GetLogFileInfo()", 610 | "component", component, 611 | "system_id", e.Client.System.ID, 612 | ) 613 | 614 | file, err := e.Client.GetLogFileInfo(component) 615 | if err != nil { 616 | level.Error(e.logger).Log( 617 | "msg", "GetLogFileInfo() failed", 618 | "component", component, 619 | "system_id", e.Client.System.ID, 620 | "error", err.Error(), 621 | ) 622 | e.IncrementErrorCounter() 623 | continue 624 | } 625 | level.Debug(e.logger).Log( 626 | "msg", "GatherMetrics() completed GetLogFileInfo()", 627 | "component", component, 628 | "system_id", e.Client.System.ID, 629 | ) 630 | 631 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 632 | logFileSize, 633 | prometheus.GaugeValue, 634 | float64(file.Info.Size()), 635 | e.Client.System.ID, 636 | file.Component, 637 | file.Path, 638 | )) 639 | 640 | level.Debug(e.logger).Log( 641 | "msg", "GatherMetrics() calls GetLogFileEventStats()", 642 | "component", component, 643 | "system_id", e.Client.System.ID, 644 | ) 645 | 646 | eventStats, err := e.Client.GetLogFileEventStats(component) 647 | if err != nil { 648 | level.Error(e.logger).Log( 649 | "msg", "GetLogFileEventStats() failed", 650 | "component", component, 651 | "system_id", e.Client.System.ID, 652 | "error", err.Error(), 653 | ) 654 | e.IncrementErrorCounter() 655 | continue 656 | } 657 | 658 | level.Debug(e.logger).Log( 659 | "msg", "GatherMetrics() completed GetLogFileEventStats()", 660 | "component", component, 661 | "system_id", e.Client.System.ID, 662 | ) 663 | 664 | for sev, sources := range eventStats { 665 | for source, count := range sources { 666 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 667 | logEventStat, 668 | prometheus.GaugeValue, 669 | float64(count), 670 | e.Client.System.ID, 671 | component, 672 | sev, 673 | source, 674 | )) 675 | } 676 | } 677 | } 678 | 679 | components = []string{ 680 | "ovsdb-server", 681 | "vswitchd-service", 682 | } 683 | 684 | for _, component := range components { 685 | level.Debug(e.logger).Log( 686 | "msg", "GatherMetrics() calls AppListCommands()", 687 | "component", component, 688 | "system_id", e.Client.System.ID, 689 | ) 690 | 691 | if cmds, err := e.Client.AppListCommands(component); err != nil { 692 | level.Error(e.logger).Log( 693 | "msg", "AppListCommands() failed", 694 | "component", component, 695 | "system_id", e.Client.System.ID, 696 | "error", err.Error(), 697 | ) 698 | e.IncrementErrorCounter() 699 | level.Debug(e.logger).Log( 700 | "msg", "GatherMetrics() completed AppListCommands()", 701 | "component", component, 702 | "system_id", e.Client.System.ID, 703 | ) 704 | } else { 705 | level.Debug(e.logger).Log( 706 | "msg", "GatherMetrics() completed AppListCommands()", 707 | "component", component, 708 | "system_id", e.Client.System.ID, 709 | ) 710 | if cmds["coverage/show"] { 711 | level.Debug(e.logger).Log( 712 | "msg", "GatherMetrics() calls GetAppCoverageMetrics()", 713 | "component", component, 714 | "system_id", e.Client.System.ID, 715 | ) 716 | 717 | if metrics, err := e.Client.GetAppCoverageMetrics(component); err != nil { 718 | level.Error(e.logger).Log( 719 | "msg", "GetAppCoverageMetrics() failed", 720 | "component", component, 721 | "system_id", e.Client.System.ID, 722 | "error", err.Error(), 723 | ) 724 | e.IncrementErrorCounter() 725 | } else { 726 | for event, metric := range metrics { 727 | for period, value := range metric { 728 | if period == "total" { 729 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 730 | covTotal, 731 | prometheus.CounterValue, 732 | value, 733 | e.Client.System.ID, 734 | component, 735 | event, 736 | )) 737 | } else { 738 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 739 | covAvg, 740 | prometheus.GaugeValue, 741 | value, 742 | e.Client.System.ID, 743 | component, 744 | event, 745 | period, 746 | )) 747 | } 748 | } 749 | } 750 | } 751 | level.Debug(e.logger).Log( 752 | "msg", "GatherMetrics() completed GetAppCoverageMetrics()", 753 | "component", component, 754 | "system_id", e.Client.System.ID, 755 | ) 756 | } 757 | if cmds["memory/show"] { 758 | level.Debug(e.logger).Log( 759 | "msg", "GatherMetrics() calls GetAppMemoryMetrics()", 760 | "component", component, 761 | "system_id", e.Client.System.ID, 762 | ) 763 | if metrics, err := e.Client.GetAppMemoryMetrics(component); err != nil { 764 | level.Error(e.logger).Log( 765 | "msg", "GetAppMemoryMetrics() failed", 766 | "component", component, 767 | "system_id", e.Client.System.ID, 768 | "error", err.Error(), 769 | ) 770 | e.IncrementErrorCounter() 771 | } else { 772 | for facility, value := range metrics { 773 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 774 | memUsage, 775 | prometheus.GaugeValue, 776 | value, 777 | e.Client.System.ID, 778 | component, 779 | facility, 780 | )) 781 | } 782 | } 783 | level.Debug(e.logger).Log( 784 | "msg", "GatherMetrics() completed GetAppMemoryMetrics()", 785 | "component", component, 786 | "system_id", e.Client.System.ID, 787 | ) 788 | } 789 | if cmds["dpif/show"] && (component == "vswitchd-service") { 790 | level.Debug(e.logger).Log( 791 | "msg", "GatherMetrics() calls GetAppDatapath()", 792 | "component", component, 793 | "system_id", e.Client.System.ID, 794 | ) 795 | 796 | if dps, brs, intfs, err := e.Client.GetAppDatapath(component); err != nil { 797 | level.Error(e.logger).Log( 798 | "msg", "GetAppDatapath() failed", 799 | "component", component, 800 | "system_id", e.Client.System.ID, 801 | "error", err.Error(), 802 | ) 803 | e.IncrementErrorCounter() 804 | } else { 805 | for _, dp := range dps { 806 | dpIntefaceCount := 0 807 | for _, br := range brs { 808 | if dp.Name != br.DatapathName { 809 | continue 810 | } 811 | brIntefaceCount := 0 812 | for _, intf := range intfs { 813 | if dp.Name != intf.DatapathName || br.Name != intf.BridgeName { 814 | continue 815 | } 816 | dpIntefaceCount += 1 817 | brIntefaceCount += 1 818 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 819 | dpInterface, 820 | prometheus.GaugeValue, 821 | 1, 822 | e.Client.System.ID, 823 | dp.Name, 824 | br.Name, 825 | intf.Name, 826 | fmt.Sprintf("%0.f", intf.OfPort), 827 | fmt.Sprintf("%0.f", intf.Index), 828 | intf.Type, 829 | )) 830 | } 831 | // Calculate the total number of interfaces per datapath 832 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 833 | dpBridgeInterfaceTotal, 834 | prometheus.GaugeValue, 835 | float64(brIntefaceCount), 836 | e.Client.System.ID, 837 | dp.Name, 838 | br.Name, 839 | )) 840 | } 841 | // Add datapath hits and misses 842 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 843 | dpLookupsHit, 844 | prometheus.CounterValue, 845 | dp.Lookups.Hit, 846 | e.Client.System.ID, 847 | dp.Name, 848 | )) 849 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 850 | dpLookupsMissed, 851 | prometheus.CounterValue, 852 | dp.Lookups.Missed, 853 | e.Client.System.ID, 854 | dp.Name, 855 | )) 856 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 857 | dpLookupsLost, 858 | prometheus.CounterValue, 859 | dp.Lookups.Lost, 860 | e.Client.System.ID, 861 | dp.Name, 862 | )) 863 | // Add datapath flows 864 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 865 | dpFlowsTotal, 866 | prometheus.GaugeValue, 867 | dp.Flows, 868 | e.Client.System.ID, 869 | dp.Name, 870 | )) 871 | // Add datapath masks 872 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 873 | dpMasksHit, 874 | prometheus.CounterValue, 875 | dp.Masks.Hit, 876 | e.Client.System.ID, 877 | dp.Name, 878 | )) 879 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 880 | dpMasksTotal, 881 | prometheus.CounterValue, 882 | dp.Masks.Total, 883 | e.Client.System.ID, 884 | dp.Name, 885 | )) 886 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 887 | dpMasksHitRatio, 888 | prometheus.GaugeValue, 889 | dp.Masks.HitRatio, 890 | e.Client.System.ID, 891 | dp.Name, 892 | )) 893 | } 894 | } 895 | level.Debug(e.logger).Log( 896 | "msg", "GatherMetrics() completed GetAppDatapath()", 897 | "component", component, 898 | "system_id", e.Client.System.ID, 899 | ) 900 | } 901 | } 902 | } 903 | 904 | level.Debug(e.logger).Log( 905 | "msg", "GatherMetrics() calls GetDbInterfaces()", 906 | "system_id", e.Client.System.ID, 907 | ) 908 | 909 | if intfs, err := e.Client.GetDbInterfaces(); err != nil { 910 | level.Error(e.logger).Log( 911 | "msg", "GetDbInterfaces() failed", 912 | "system_id", e.Client.System.ID, 913 | "error", err.Error(), 914 | ) 915 | e.IncrementErrorCounter() 916 | } else { 917 | for _, intf := range intfs { 918 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 919 | interfaceMain, 920 | prometheus.GaugeValue, 921 | 1, 922 | e.Client.System.ID, 923 | intf.UUID, 924 | intf.Name, 925 | )) 926 | var adminState float64 927 | switch intf.AdminState { 928 | case "down": 929 | adminState = 0 930 | case "up": 931 | adminState = 1 932 | default: 933 | adminState = 2 934 | } 935 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 936 | interfaceAdminState, 937 | prometheus.GaugeValue, 938 | adminState, 939 | e.Client.System.ID, 940 | intf.UUID, 941 | )) 942 | var linkState float64 943 | switch intf.LinkState { 944 | case "down": 945 | linkState = 0 946 | case "up": 947 | linkState = 1 948 | default: 949 | linkState = 2 950 | } 951 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 952 | interfaceLinkState, 953 | prometheus.GaugeValue, 954 | linkState, 955 | e.Client.System.ID, 956 | intf.UUID, 957 | )) 958 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 959 | interfaceIngressPolicingBurst, 960 | prometheus.GaugeValue, 961 | intf.IngressPolicingBurst, 962 | e.Client.System.ID, 963 | intf.UUID, 964 | )) 965 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 966 | interfaceIngressPolicingRate, 967 | prometheus.GaugeValue, 968 | intf.IngressPolicingRate, 969 | e.Client.System.ID, 970 | intf.UUID, 971 | )) 972 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 973 | interfaceMacInUse, 974 | prometheus.GaugeValue, 975 | 1, 976 | e.Client.System.ID, 977 | intf.UUID, 978 | intf.MacInUse, 979 | )) 980 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 981 | interfaceMtu, 982 | prometheus.GaugeValue, 983 | intf.Mtu, 984 | e.Client.System.ID, 985 | intf.UUID, 986 | )) 987 | var linkDuplex float64 988 | switch intf.Duplex { 989 | case "half": 990 | linkDuplex = 1 991 | case "full": 992 | linkDuplex = 2 993 | default: 994 | linkDuplex = 0 995 | } 996 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 997 | interfaceDuplex, 998 | prometheus.GaugeValue, 999 | linkDuplex, 1000 | e.Client.System.ID, 1001 | intf.UUID, 1002 | )) 1003 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1004 | interfaceOfPort, 1005 | prometheus.GaugeValue, 1006 | intf.OfPort, 1007 | e.Client.System.ID, 1008 | intf.UUID, 1009 | )) 1010 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1011 | interfaceIfIndex, 1012 | prometheus.GaugeValue, 1013 | intf.IfIndex, 1014 | e.Client.System.ID, 1015 | intf.UUID, 1016 | )) 1017 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1018 | interfaceLocalIndex, 1019 | prometheus.GaugeValue, 1020 | intf.Index, 1021 | e.Client.System.ID, 1022 | intf.UUID, 1023 | )) 1024 | for key, value := range intf.Statistics { 1025 | switch key { 1026 | case "rx_crc_err": 1027 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1028 | interfaceStatRxCrcError, 1029 | prometheus.CounterValue, 1030 | float64(value), 1031 | e.Client.System.ID, 1032 | intf.UUID, 1033 | )) 1034 | case "rx_dropped": 1035 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1036 | interfaceStatRxDropped, 1037 | prometheus.CounterValue, 1038 | float64(value), 1039 | e.Client.System.ID, 1040 | intf.UUID, 1041 | )) 1042 | case "rx_frame_err": 1043 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1044 | interfaceStatRxFrameError, 1045 | prometheus.CounterValue, 1046 | float64(value), 1047 | e.Client.System.ID, 1048 | intf.UUID, 1049 | )) 1050 | case "rx_over_err": 1051 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1052 | interfaceStatRxOverrunError, 1053 | prometheus.CounterValue, 1054 | float64(value), 1055 | e.Client.System.ID, 1056 | intf.UUID, 1057 | )) 1058 | case "rx_errors": 1059 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1060 | interfaceStatRxErrorsTotal, 1061 | prometheus.CounterValue, 1062 | float64(value), 1063 | e.Client.System.ID, 1064 | intf.UUID, 1065 | )) 1066 | case "rx_packets": 1067 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1068 | interfaceStatRxPackets, 1069 | prometheus.CounterValue, 1070 | float64(value), 1071 | e.Client.System.ID, 1072 | intf.UUID, 1073 | )) 1074 | case "rx_bytes": 1075 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1076 | interfaceStatRxBytes, 1077 | prometheus.CounterValue, 1078 | float64(value), 1079 | e.Client.System.ID, 1080 | intf.UUID, 1081 | )) 1082 | case "tx_packets": 1083 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1084 | interfaceStatTxPackets, 1085 | prometheus.CounterValue, 1086 | float64(value), 1087 | e.Client.System.ID, 1088 | intf.UUID, 1089 | )) 1090 | case "tx_bytes": 1091 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1092 | interfaceStatTxBytes, 1093 | prometheus.CounterValue, 1094 | float64(value), 1095 | e.Client.System.ID, 1096 | intf.UUID, 1097 | )) 1098 | case "tx_dropped": 1099 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1100 | interfaceStatTxDropped, 1101 | prometheus.CounterValue, 1102 | float64(value), 1103 | e.Client.System.ID, 1104 | intf.UUID, 1105 | )) 1106 | case "tx_errors": 1107 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1108 | interfaceStatTxErrorsTotal, 1109 | prometheus.CounterValue, 1110 | float64(value), 1111 | e.Client.System.ID, 1112 | intf.UUID, 1113 | )) 1114 | case "collisions": 1115 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1116 | interfaceStatCollisions, 1117 | prometheus.CounterValue, 1118 | float64(value), 1119 | e.Client.System.ID, 1120 | intf.UUID, 1121 | )) 1122 | case "rx_missed_errors": 1123 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1124 | interfaceStatRxMissedErrors, 1125 | prometheus.CounterValue, 1126 | float64(value), 1127 | e.Client.System.ID, 1128 | intf.UUID, 1129 | )) 1130 | case "rx_multicast_packets": 1131 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1132 | interfaceStateMulticastPackets, 1133 | prometheus.CounterValue, 1134 | float64(value), 1135 | e.Client.System.ID, 1136 | intf.UUID, 1137 | )) 1138 | default: 1139 | level.Debug(e.logger).Log( 1140 | "msg", "detected malformed interface statistics", 1141 | "system_id", e.Client.System.ID, 1142 | "key", key, 1143 | "value", value, 1144 | "error", "OVS interface statistics has unsupported key", 1145 | ) 1146 | } 1147 | } 1148 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1149 | interfaceLinkResets, 1150 | prometheus.CounterValue, 1151 | intf.LinkResets, 1152 | e.Client.System.ID, 1153 | intf.UUID, 1154 | )) 1155 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1156 | interfaceLinkSpeed, 1157 | prometheus.GaugeValue, 1158 | intf.LinkSpeed, 1159 | e.Client.System.ID, 1160 | intf.UUID, 1161 | )) 1162 | for key, value := range intf.Status { 1163 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1164 | interfaceStatusKeyValuePair, 1165 | prometheus.GaugeValue, 1166 | 1, 1167 | e.Client.System.ID, 1168 | intf.UUID, 1169 | key, 1170 | value, 1171 | )) 1172 | } 1173 | for key, value := range intf.Options { 1174 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1175 | interfaceOptionsKeyValuePair, 1176 | prometheus.GaugeValue, 1177 | 1, 1178 | e.Client.System.ID, 1179 | intf.UUID, 1180 | key, 1181 | value, 1182 | )) 1183 | } 1184 | for key, value := range intf.ExternalIDs { 1185 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1186 | interfaceExternalIdKeyValuePair, 1187 | prometheus.GaugeValue, 1188 | 1, 1189 | e.Client.System.ID, 1190 | intf.UUID, 1191 | key, 1192 | value, 1193 | )) 1194 | } 1195 | } 1196 | } 1197 | 1198 | level.Debug(e.logger).Log( 1199 | "msg", "GatherMetrics() completed GetDbInterfaces()", 1200 | "system_id", e.Client.System.ID, 1201 | ) 1202 | 1203 | components = []string{ 1204 | "ovsdb-server", 1205 | } 1206 | 1207 | for _, component := range components { 1208 | level.Debug(e.logger).Log( 1209 | "msg", "GatherMetrics() calls IsDefaultPortUp()", 1210 | "component", component, 1211 | "system_id", e.Client.System.ID, 1212 | ) 1213 | defaultPortUp, err := e.Client.IsDefaultPortUp(component) 1214 | if err != nil { 1215 | level.Error(e.logger).Log( 1216 | "msg", "IsDefaultPortUp() failed", 1217 | "component", component, 1218 | "system_id", e.Client.System.ID, 1219 | "error", err.Error(), 1220 | ) 1221 | e.IncrementErrorCounter() 1222 | } 1223 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1224 | networkPortUp, 1225 | prometheus.GaugeValue, 1226 | float64(defaultPortUp), 1227 | e.Client.System.ID, 1228 | component, 1229 | "default", 1230 | )) 1231 | level.Debug(e.logger).Log( 1232 | "msg", "GatherMetrics() completed IsDefaultPortUp()", 1233 | "component", component, 1234 | "system_id", e.Client.System.ID, 1235 | ) 1236 | 1237 | level.Debug(e.logger).Log( 1238 | "msg", "GatherMetrics() calls IsSslPortUp()", 1239 | "component", component, 1240 | "system_id", e.Client.System.ID, 1241 | ) 1242 | sslPortUp, err := e.Client.IsSslPortUp(component) 1243 | if err != nil { 1244 | level.Error(e.logger).Log( 1245 | "msg", "IsSslPortUp() failed", 1246 | "component", component, 1247 | "system_id", e.Client.System.ID, 1248 | "error", err.Error(), 1249 | ) 1250 | e.IncrementErrorCounter() 1251 | } 1252 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1253 | networkPortUp, 1254 | prometheus.GaugeValue, 1255 | float64(sslPortUp), 1256 | e.Client.System.ID, 1257 | component, 1258 | "ssl", 1259 | )) 1260 | level.Debug(e.logger).Log( 1261 | "msg", "GatherMetrics() completed IsSslPortUp()", 1262 | "component", component, 1263 | "system_id", e.Client.System.ID, 1264 | ) 1265 | } 1266 | 1267 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1268 | up, 1269 | prometheus.GaugeValue, 1270 | float64(upValue), 1271 | )) 1272 | 1273 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1274 | info, 1275 | prometheus.GaugeValue, 1276 | 1, 1277 | e.Client.System.ID, e.Client.System.RunDir, e.Client.System.Hostname, 1278 | e.Client.System.Type, e.Client.System.Version, 1279 | e.Client.Database.Vswitch.Version, e.Client.Database.Vswitch.Schema.Version, 1280 | )) 1281 | 1282 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1283 | requestErrors, 1284 | prometheus.CounterValue, 1285 | float64(e.errors), 1286 | e.Client.System.ID, 1287 | )) 1288 | 1289 | e.metrics = append(e.metrics, prometheus.MustNewConstMetric( 1290 | nextPoll, 1291 | prometheus.CounterValue, 1292 | float64(e.nextCollectionTicker), 1293 | e.Client.System.ID, 1294 | )) 1295 | 1296 | e.nextCollectionTicker = time.Now().Add(time.Duration(e.pollInterval) * time.Second).Unix() 1297 | 1298 | level.Debug(e.logger).Log( 1299 | "msg", "GatherMetrics() returns", 1300 | "system_id", e.Client.System.ID, 1301 | ) 1302 | 1303 | return 1304 | } 1305 | 1306 | func init() { 1307 | prometheus.MustRegister(version.NewCollector(namespace + "_exporter")) 1308 | } 1309 | 1310 | // GetVersionInfo returns exporter info. 1311 | func GetVersionInfo() string { 1312 | return version.Info() 1313 | } 1314 | 1315 | // GetVersionBuildContext returns exporter build context. 1316 | func GetVersionBuildContext() string { 1317 | return version.BuildContext() 1318 | } 1319 | 1320 | // GetVersion returns exporter version. 1321 | func GetVersion() string { 1322 | return version.Version 1323 | } 1324 | 1325 | // GetRevision returns exporter revision. 1326 | func GetRevision() string { 1327 | return version.Revision 1328 | } 1329 | 1330 | // GetExporterName returns exporter name. 1331 | func GetExporterName() string { 1332 | return appName 1333 | } 1334 | 1335 | // SetPollInterval sets exporter's polling interval. 1336 | func (e *Exporter) SetPollInterval(i int64) { 1337 | e.pollInterval = i 1338 | } 1339 | --------------------------------------------------------------------------------