Refactor exporter config (#68)

Switch exporter over to standard Prometheus exporter flags and logging.
This eliminates the need for a configuraion file.

Signed-off-by: SuperQ <superq@gmail.com>
This commit is contained in:
Ben Kochie 2022-10-03 11:16:00 +02:00 committed by GitHub
parent ca94177954
commit c8d3e48f3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 221 additions and 249 deletions

10
.golangci.yml Normal file
View file

@ -0,0 +1,10 @@
---
issues:
exclude-rules:
- path: _test.go
linters:
- errcheck
linters-settings:
errcheck:
exclude: scripts/errcheck_excludes.txt

View file

@ -15,26 +15,34 @@ smartmontools >= 7.0, because export to json [released in 7.0](https://www.smart
# Configuration
## Command line options
* `--config=/path/to/file.yaml`: Path to configuration file, default `/etc/smartctl_exporter.yaml`
* `--verbose`: verbosed log, default no
* `--debug`: Debug logging, default no
* `--version`: Show version and exit
## Configuration file
Example content:
The exporter will scan the system for available devices if no `--smartctl.device` flags are used.
```
smartctl_exporter:
bind_to: "[::1]:9633"
url_path: "/metrics"
fake_json: no
smartctl_location: /usr/sbin/smartctl
collect_not_more_than_period: 120s
devices:
- /dev/sda
- /dev/sdb
- /dev/sdc
- /dev/sdd
- /dev/sde
- /dev/sdf
usage: smartctl_exporter [<flags>]
Flags:
-h, --help Show context-sensitive help (also try --help-long and --help-man).
--smartctl.path="/usr/sbin/smartctl"
The path to the smartctl binary
--smartctl.interval=60s The interval between smarctl polls
--smartctl.device=SMARTCTL.DEVICE ...
The device to monitor (repeatable)
--web.listen-address=":9633"
Address to listen on for web interface and telemetry
--web.telemetry-path="/metrics"
Path under which to expose metrics
--web.config.file="" [EXPERIMENTAL] Path to configuration file that can enable TLS or authentication.
--log.level=info Only log messages with the given severity or above. One of: [debug, info, warn,
error]
--log.format=logfmt Output format of log messages. One of: [logfmt, json]
--version Show application version.
```
`fake_json` used for debugging.
## TLS and basic authentication
This exporter supports TLS and basic authentication.
To use TLS and/or basic authentication, you need to pass a configuration file
using the `--web.config.file` parameter. The format of the file is described
[in the exporter-toolkit repository](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md).

18
go.mod
View file

@ -3,21 +3,35 @@ module github.com/prometheus-community/smartctl_exporter
go 1.17
require (
github.com/go-kit/log v0.2.1
github.com/prometheus/client_golang v1.13.0
github.com/prometheus/common v0.37.0
github.com/prometheus/exporter-toolkit v0.7.1
github.com/tidwall/gjson v1.14.3
gopkg.in/yaml.v2 v2.4.0
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

22
go.sum
View file

@ -34,9 +34,11 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -52,6 +54,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -64,9 +67,12 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -127,6 +133,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -153,10 +160,13 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/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_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@ -173,9 +183,12 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/exporter-toolkit v0.7.1 h1:c6RXaK8xBVercEeUQ4tRNL8UGWzDHfvj9dseo1FcK1Y=
github.com/prometheus/exporter-toolkit v0.7.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@ -191,6 +204,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@ -212,6 +226,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -270,8 +286,10 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -279,6 +297,7 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -339,6 +358,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -408,6 +428,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -464,6 +485,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -1,68 +0,0 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
)
// Logger is logger
type Logger struct {
verbose bool
debug bool
}
func newLogger(verbose bool, debug bool) Logger {
logger := Logger{verbose: verbose, debug: debug}
return logger
}
// Msg formatted message
func (i Logger) print(prefix string, format string, values ...interface{}) {
fmt.Printf(fmt.Sprintf("[%s] %s\n", prefix, format), values...)
}
// Info log message
func (i Logger) Info(format string, values ...interface{}) {
i.print("Info", format, values...)
}
// Warning log message
func (i Logger) Warning(format string, values ...interface{}) {
i.print("Warning", format, values...)
}
// Error log message
func (i Logger) Error(format string, values ...interface{}) {
i.print("Error", format, values...)
}
// Panic log message
func (i Logger) Panic(format string, values ...interface{}) {
i.print("Panic", format, values...)
}
// Verbose log message
func (i Logger) Verbose(format string, values ...interface{}) {
if i.verbose {
i.print("Verbose", format, values...)
}
}
// Debug log message
func (i Logger) Debug(format string, values ...interface{}) {
if i.debug {
i.print("Debug", format, values...)
}
}

123
main.go
View file

@ -14,21 +14,30 @@
package main
import (
"log"
"net/http"
"os"
"time"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
options Options
logger Logger
"github.com/prometheus/common/promlog"
"github.com/prometheus/common/promlog/flag"
"github.com/prometheus/common/version"
"github.com/prometheus/exporter-toolkit/web"
webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
// SMARTctlManagerCollector implements the Collector interface.
type SMARTctlManagerCollector struct {
CollectPeriod string
CollectPeriodDuration time.Duration
Devices []string
logger log.Logger
}
// Describe sends the super-set of all possible descriptors of metrics
@ -39,43 +48,101 @@ func (i SMARTctlManagerCollector) Describe(ch chan<- *prometheus.Desc) {
// Collect is called by the Prometheus registry when collecting metrics.
func (i SMARTctlManagerCollector) Collect(ch chan<- prometheus.Metric) {
info := NewSMARTctlInfo(ch)
for _, device := range options.SMARTctl.Devices {
if json, err := readData(device); err == nil {
for _, device := range i.Devices {
if json, err := readData(i.logger, device); err == nil {
info.SetJSON(json)
smart := NewSMARTctl(json, ch)
smart := NewSMARTctl(i.logger, json, ch)
smart.Collect()
} else {
logger.Error(err.Error())
level.Error(i.logger).Log("msg", "Error collecting SMART data", "err", err.Error())
}
}
info.Collect()
}
func init() {
options = loadOptions()
if len(options.SMARTctl.Devices) == 0 {
logger.Debug("No devices specified, trying to load them automatically")
json := readSMARTctlDevices()
devices := json.Get("devices").Array()
for _, d := range devices {
device := d.Get("name").String()
logger.Debug("Found device: %s", device)
options.SMARTctl.Devices = append(options.SMARTctl.Devices, device)
}
}
}
var (
smartctlPath = kingpin.Flag("smartctl.path",
"The path to the smartctl binary",
).Default("/usr/sbin/smartctl").String()
smartctlInterval = kingpin.Flag("smartctl.interval",
"The interval between smarctl polls",
).Default("60s").Duration()
smartctlDevices = kingpin.Flag("smartctl.device",
"The device to monitor (repeatable)",
).Strings()
smartctlFakeData = kingpin.Flag("smartctl.fake-data",
"The device to monitor (repeatable)",
).Default("false").Hidden().Bool()
)
func main() {
listenAddress := kingpin.Flag("web.listen-address",
"Address to listen on for web interface and telemetry",
).Default(":9633").String()
metricsPath := kingpin.Flag(
"web.telemetry-path", "Path under which to expose metrics",
).Default("/metrics").String()
webConfig := webflag.AddFlags(kingpin.CommandLine)
promlogConfig := &promlog.Config{}
flag.AddFlags(kingpin.CommandLine, promlogConfig)
kingpin.Version(version.Print("smartctl_exporter"))
kingpin.HelpFlag.Short('h')
kingpin.Parse()
logger := promlog.New(promlogConfig)
level.Info(logger).Log("msg", "Starting systemd_exporter", "version", version.Info())
level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext())
devices := *smartctlDevices
if len(devices) == 0 {
level.Info(logger).Log("msg", "No devices specified, trying to load them automatically")
json := readSMARTctlDevices(logger)
scannedDevices := json.Get("devices").Array()
for _, d := range scannedDevices {
device := d.Get("name").String()
level.Info(logger).Log("msg", "Found device", "device", device)
devices = append(devices, device)
}
}
if len(devices) == 0 {
level.Error(logger).Log("msg", "No devices found")
os.Exit(1)
}
collector := SMARTctlManagerCollector{
Devices: devices,
logger: logger,
}
reg := prometheus.NewPedanticRegistry()
reg.MustRegister(
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
collectors.NewGoCollector(),
)
prometheus.WrapRegistererWithPrefix("", reg).MustRegister(SMARTctlManagerCollector{})
prometheus.WrapRegistererWithPrefix("", reg).MustRegister(collector)
logger.Info("Starting on %s%s", options.SMARTctl.BindTo, options.SMARTctl.URLPath)
http.Handle(options.SMARTctl.URLPath, promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
log.Fatal(http.ListenAndServe(options.SMARTctl.BindTo, nil))
level.Info(logger).Log("msg", "Listening on", "address", *listenAddress)
http.Handle(*metricsPath, promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(`<html>
<head><title>Smartctl Exporter</title></head>
<body>
<h1>Smartctl Exporter</h1>
<p><a href="` + *metricsPath + `">Metrics</a></p>
</body>
</html>`))
if err != nil {
level.Error(logger).Log("msg", "Couldn't write response", "err", err)
}
})
srv := &http.Server{Addr: *listenAddress}
if err := web.ListenAndServe(srv, *webConfig, logger); err != nil {
level.Error(logger).Log("err", err)
os.Exit(1)
}
}

View file

@ -1,91 +0,0 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"time"
yaml "gopkg.in/yaml.v2"
)
var (
exporterVersion = "0.5"
)
// SMARTOptions is a inner representation of a options
type SMARTOptions struct {
BindTo string `yaml:"bind_to"`
URLPath string `yaml:"url_path"`
FakeJSON bool `yaml:"fake_json"`
SMARTctlLocation string `yaml:"smartctl_location"`
CollectPeriod string `yaml:"collect_not_more_than_period"`
CollectPeriodDuration time.Duration
Devices []string `yaml:"devices"`
}
// Options is a representation of a options
type Options struct {
SMARTctl SMARTOptions `yaml:"smartctl_exporter"`
}
// Parse options from yaml config file
func loadOptions() Options {
configFile := flag.String("config", "/etc/smartctl_exporter.yaml", "Path to smartctl_exporter config file")
verbose := flag.Bool("verbose", false, "Verbose log output")
debug := flag.Bool("debug", false, "Debug log output")
version := flag.Bool("version", false, "Show application version and exit")
flag.Parse()
if *version {
fmt.Printf("smartctl_exporter version: %s\n", exporterVersion)
os.Exit(0)
}
logger = newLogger(*verbose, *debug)
logger.Verbose("Read options from %s\n", *configFile)
yamlFile, err := ioutil.ReadFile(*configFile)
if err != nil {
logger.Panic("Failed read %s: %s", configFile, err)
}
opts := Options{
SMARTOptions{
BindTo: "9633",
URLPath: "/metrics",
FakeJSON: false,
SMARTctlLocation: "/usr/sbin/smartctl",
CollectPeriod: "60s",
Devices: []string{},
},
}
if yaml.Unmarshal(yamlFile, &opts) != nil {
logger.Panic("Failed parse %s: %s", configFile, err)
}
d, err := time.ParseDuration(opts.SMARTctl.CollectPeriod)
if err != nil {
logger.Panic("Failed read collect_not_more_than_period (%s): %s", opts.SMARTctl.CollectPeriod, err)
}
opts.SMARTctl.CollectPeriodDuration = d
logger.Debug("Parsed options: %s", opts)
return opts
}

View file

@ -21,6 +21,8 @@ import (
"strings"
"time"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/tidwall/gjson"
)
@ -47,50 +49,50 @@ func parseJSON(data string) gjson.Result {
}
// Reading fake smartctl json
func readFakeSMARTctl(device string) gjson.Result {
func readFakeSMARTctl(logger log.Logger, device string) gjson.Result {
s := strings.Split(device, "/")
filename := fmt.Sprintf("debug/%s.json", s[len(s)-1])
logger.Verbose("Read fake S.M.A.R.T. data from json: %s", filename)
level.Debug(logger).Log("msg", "Read fake S.M.A.R.T. data from json", "filename", filename)
jsonFile, err := ioutil.ReadFile(filename)
if err != nil {
logger.Error("Fake S.M.A.R.T. data reading error: %s", err)
level.Error(logger).Log("msg", "Fake S.M.A.R.T. data reading error", "err", err)
return parseJSON("{}")
}
return parseJSON(string(jsonFile))
}
// Get json from smartctl and parse it
func readSMARTctl(device string) (gjson.Result, bool) {
logger.Debug("Collecting S.M.A.R.T. counters, device: %s", device)
out, err := exec.Command(options.SMARTctl.SMARTctlLocation, "--json", "--xall", device).Output()
func readSMARTctl(logger log.Logger, device string) (gjson.Result, bool) {
level.Debug(logger).Log("msg", "Collecting S.M.A.R.T. counters", "device", device)
out, err := exec.Command(*smartctlPath, "--json", "--xall", device).Output()
if err != nil {
logger.Warning("S.M.A.R.T. output reading error: %s", err)
level.Warn(logger).Log("msg", "S.M.A.R.T. output reading", "err", err)
}
json := parseJSON(string(out))
rcOk := resultCodeIsOk(json.Get("smartctl.exit_status").Int())
jsonOk := jsonIsOk(json)
rcOk := resultCodeIsOk(logger, json.Get("smartctl.exit_status").Int())
jsonOk := jsonIsOk(logger, json)
return json, rcOk && jsonOk
}
func readSMARTctlDevices() gjson.Result {
logger.Debug("Collecting devices")
out, err := exec.Command(options.SMARTctl.SMARTctlLocation, "--json", "--scan-open").Output()
func readSMARTctlDevices(logger log.Logger) gjson.Result {
level.Debug(logger).Log("msg", "Collecting devices")
out, err := exec.Command(*smartctlPath, "--json", "--scan-open").Output()
if err != nil {
logger.Warning("S.M.A.R.T. output reading error: %s", err)
level.Warn(logger).Log("msg", "S.M.A.R.T. output reading error", "err", err)
}
return parseJSON(string(out))
}
// Select json source and parse
func readData(device string) (gjson.Result, error) {
if options.SMARTctl.FakeJSON {
return readFakeSMARTctl(device), nil
func readData(logger log.Logger, device string) (gjson.Result, error) {
if *smartctlFakeData {
return readFakeSMARTctl(logger, device), nil
}
if _, err := os.Stat(device); err == nil {
cacheValue, cacheOk := jsonCache[device]
if !cacheOk || time.Now().After(cacheValue.LastCollect.Add(options.SMARTctl.CollectPeriodDuration)) {
json, ok := readSMARTctl(device)
if !cacheOk || time.Now().After(cacheValue.LastCollect.Add(*smartctlInterval)) {
json, ok := readSMARTctl(logger, device)
if ok {
jsonCache[device] = JSONCache{JSON: json, LastCollect: time.Now()}
return jsonCache[device].JSON, nil
@ -103,48 +105,48 @@ func readData(device string) (gjson.Result, error) {
}
// Parse smartctl return code
func resultCodeIsOk(SMARTCtlResult int64) bool {
func resultCodeIsOk(logger log.Logger, SMARTCtlResult int64) bool {
result := true
if SMARTCtlResult > 0 {
b := SMARTCtlResult
if (b & 1) != 0 {
logger.Error("Command line did not parse.")
level.Error(logger).Log("msg", "Command line did not parse.")
result = false
}
if (b & (1 << 1)) != 0 {
logger.Error("Device open failed, device did not return an IDENTIFY DEVICE structure, or device is in a low-power mode")
level.Error(logger).Log("msg", "Device open failed, device did not return an IDENTIFY DEVICE structure, or device is in a low-power mode")
result = false
}
if (b & (1 << 2)) != 0 {
logger.Warning("Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure")
level.Warn(logger).Log("msg", "Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure")
}
if (b & (1 << 3)) != 0 {
logger.Warning("SMART status check returned 'DISK FAILING'.")
level.Warn(logger).Log("msg", "SMART status check returned 'DISK FAILING'.")
}
if (b & (1 << 4)) != 0 {
logger.Warning("We found prefail Attributes <= threshold.")
level.Warn(logger).Log("msg", "We found prefail Attributes <= threshold.")
}
if (b & (1 << 5)) != 0 {
logger.Warning("SMART status check returned 'DISK OK' but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past.")
level.Warn(logger).Log("msg", "SMART status check returned 'DISK OK' but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past.")
}
if (b & (1 << 6)) != 0 {
logger.Warning("The device error log contains records of errors.")
level.Warn(logger).Log("msg", "The device error log contains records of errors.")
}
if (b & (1 << 7)) != 0 {
logger.Warning("The device self-test log contains records of errors. [ATA only] Failed self-tests outdated by a newer successful extended self-test are ignored.")
level.Warn(logger).Log("msg", "The device self-test log contains records of errors. [ATA only] Failed self-tests outdated by a newer successful extended self-test are ignored.")
}
}
return result
}
// Check json
func jsonIsOk(json gjson.Result) bool {
func jsonIsOk(logger log.Logger, json gjson.Result) bool {
messages := json.Get("smartctl.messages")
// logger.Debug(messages.String())
if messages.Exists() {
for _, message := range messages.Array() {
if message.Get("severity").String() == "error" {
logger.Error(message.Get("string").String())
level.Error(logger).Log("msg", message.Get("string").String())
return false
}
}

View file

@ -0,0 +1,4 @@
// Used in HTTP handlers, any error is handled by the server itself.
(net/http.ResponseWriter).Write
// Never check for logger errors.
(github.com/go-kit/log.Logger).Log

View file

@ -17,6 +17,8 @@ import (
"fmt"
"strings"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/tidwall/gjson"
)
@ -33,26 +35,28 @@ type SMARTDevice struct {
type SMARTctl struct {
ch chan<- prometheus.Metric
json gjson.Result
logger log.Logger
device SMARTDevice
}
// NewSMARTctl is smartctl constructor
func NewSMARTctl(json gjson.Result, ch chan<- prometheus.Metric) SMARTctl {
smart := SMARTctl{}
smart.ch = ch
smart.json = json
smart.device = SMARTDevice{
device: strings.TrimSpace(smart.json.Get("device.name").String()),
serial: strings.TrimSpace(smart.json.Get("serial_number").String()),
family: strings.TrimSpace(smart.json.Get("model_family").String()),
model: strings.TrimSpace(smart.json.Get("model_name").String()),
func NewSMARTctl(logger log.Logger, json gjson.Result, ch chan<- prometheus.Metric) SMARTctl {
return SMARTctl{
ch: ch,
json: json,
logger: logger,
device: SMARTDevice{
device: strings.TrimSpace(json.Get("device.name").String()),
serial: strings.TrimSpace(json.Get("serial_number").String()),
family: strings.TrimSpace(json.Get("model_family").String()),
model: strings.TrimSpace(json.Get("model_name").String()),
},
}
return smart
}
// Collect metrics
func (smart *SMARTctl) Collect() {
logger.Verbose("Collecting metrics from %s: %s, %s", smart.device.device, smart.device.family, smart.device.model)
level.Debug(smart.logger).Log("msg", "Collecting metrics from", "device", smart.device.device, "family", smart.device.family, "model", smart.device.model)
smart.mineExitStatus()
smart.mineDevice()
smart.mineCapacity()