mirror of
https://github.com/prometheus-community/smartctl_exporter.git
synced 2024-11-23 01:43:07 +01:00
apply merges
This commit is contained in:
commit
e27581d56a
8 changed files with 543 additions and 1 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,3 +4,5 @@ debug
|
||||||
*.json
|
*.json
|
||||||
|
|
||||||
Manifest
|
Manifest
|
||||||
|
.idea
|
||||||
|
smartctl_exporter
|
||||||
|
|
9
go.mod
Normal file
9
go.mod
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module github.com/Sheridan/smartctl_exporter
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/prometheus/client_golang v1.7.1
|
||||||
|
github.com/tidwall/gjson v1.6.0
|
||||||
|
gopkg.in/yaml.v2 v2.3.0
|
||||||
|
)
|
118
go.sum
Normal file
118
go.sum
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
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/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=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
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/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
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=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/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/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=
|
||||||
|
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
||||||
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||||
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
|
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 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||||
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
|
||||||
|
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||||
|
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||||
|
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||||
|
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||||
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
|
||||||
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
11
main.go
11
main.go
|
@ -39,6 +39,17 @@ func (i SMARTctlManagerCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
options = loadOptions()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
169
metrics.go
169
metrics.go
|
@ -140,6 +140,105 @@ var (
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
metricDevicePercentageUsed = prometheus.NewDesc(
|
||||||
|
"smartctl_device_percentage_used",
|
||||||
|
"Device write percentage used",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceAvailableSpare = prometheus.NewDesc(
|
||||||
|
"smartctl_device_available_spare",
|
||||||
|
"Normalized percentage (0 to 100%) of the remaining spare capacity available",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceAvailableSpareThreshold = prometheus.NewDesc(
|
||||||
|
"smartctl_device_available_spare_threshold",
|
||||||
|
"When the Available Spare falls below the threshold indicated in this field, an asynchronous event completion may occur. The value is indicated as a normalized percentage (0 to 100%)",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceCriticalWarning = prometheus.NewDesc(
|
||||||
|
"smartctl_device_critical_warning",
|
||||||
|
"This field indicates critical warnings for the state of the controller",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceMediaErrors = prometheus.NewDesc(
|
||||||
|
"smartctl_device_media_errors",
|
||||||
|
"Contains the number of occurrences where the controller detected an unrecovered data integrity error. Errors such as uncorrectable ECC, CRC checksum failure, or LBA tag mismatch are included in this field",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceNumErrLogEntries = prometheus.NewDesc(
|
||||||
|
"smartctl_device_num_err_log_entries",
|
||||||
|
"Contains the number of Error Information log entries over the life of the controller",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceBytesRead = prometheus.NewDesc(
|
||||||
|
"smartctl_device_bytes_read",
|
||||||
|
"",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceBytesWritten = prometheus.NewDesc(
|
||||||
|
"smartctl_device_bytes_written",
|
||||||
|
"",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceSmartStatus = prometheus.NewDesc(
|
||||||
|
"smartctl_device_smart_status",
|
||||||
|
"General smart status",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
metricDeviceExitStatus = prometheus.NewDesc(
|
metricDeviceExitStatus = prometheus.NewDesc(
|
||||||
"smartctl_device_smartctl_exit_status",
|
"smartctl_device_smartctl_exit_status",
|
||||||
"Exit status of smartctl on device",
|
"Exit status of smartctl on device",
|
||||||
|
@ -151,6 +250,17 @@ var (
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
metricDeviceState = prometheus.NewDesc(
|
||||||
|
"smartctl_device_state",
|
||||||
|
"Device state (0=active, 1=standby, 2=sleep, 3=dst, 4=offline, 5=sct)",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
metricDeviceStatistics = prometheus.NewDesc(
|
metricDeviceStatistics = prometheus.NewDesc(
|
||||||
"smartctl_device_statistics",
|
"smartctl_device_statistics",
|
||||||
"Device statistics",
|
"Device statistics",
|
||||||
|
@ -177,6 +287,17 @@ var (
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
metricDeviceStatus = prometheus.NewDesc(
|
||||||
|
"smartctl_device_status",
|
||||||
|
"Device status",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
metricAvailableSpare = prometheus.NewDesc(
|
metricAvailableSpare = prometheus.NewDesc(
|
||||||
"available_spare",
|
"available_spare",
|
||||||
"Available spare",
|
"Available spare",
|
||||||
|
@ -188,6 +309,18 @@ var (
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
metricDeviceErrorLogCount = prometheus.NewDesc(
|
||||||
|
"smartctl_device_error_log_count",
|
||||||
|
"Device SMART error log count",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
"error_log_type",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
metricMediaErrors = prometheus.NewDesc(
|
metricMediaErrors = prometheus.NewDesc(
|
||||||
"media_errors",
|
"media_errors",
|
||||||
"Media errors counter",
|
"Media errors counter",
|
||||||
|
@ -199,6 +332,18 @@ var (
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
metricDeviceSelfTestLogCount = prometheus.NewDesc(
|
||||||
|
"smartctl_device_self_test_log_count",
|
||||||
|
"Device SMART self test log count",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
"self_test_log_type",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
metricSmartStatus = prometheus.NewDesc(
|
metricSmartStatus = prometheus.NewDesc(
|
||||||
"smart_status",
|
"smart_status",
|
||||||
"Smart status",
|
"Smart status",
|
||||||
|
@ -210,4 +355,28 @@ var (
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
metricDeviceSelfTestLogErrorCount = prometheus.NewDesc(
|
||||||
|
"smartctl_device_self_test_log_error_count",
|
||||||
|
"Device SMART self test log error count",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
"self_test_log_type",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceERCSeconds = prometheus.NewDesc(
|
||||||
|
"smartctl_device_erc_seconds",
|
||||||
|
"Device SMART Error Recovery Control Seconds",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
"op_type",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,6 +59,15 @@ func readSMARTctl(device string) (gjson.Result, bool) {
|
||||||
return json, rcOk && jsonOk
|
return json, rcOk && jsonOk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readSMARTctlDevices() gjson.Result {
|
||||||
|
logger.Debug("Collecting devices")
|
||||||
|
out, err := exec.Command(options.SMARTctl.SMARTctlLocation, "--json", "--scan-open").Output()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("S.M.A.R.T. output reading error: %s", err)
|
||||||
|
}
|
||||||
|
return parseJSON(string(out))
|
||||||
|
}
|
||||||
|
|
||||||
// Select json source and parse
|
// Select json source and parse
|
||||||
func readData(device string) (gjson.Result, error) {
|
func readData(device string) (gjson.Result, error) {
|
||||||
if options.SMARTctl.FakeJSON {
|
if options.SMARTctl.FakeJSON {
|
||||||
|
|
224
smartctl.go
224
smartctl.go
|
@ -49,9 +49,24 @@ func (smart *SMARTctl) Collect() {
|
||||||
smart.mineRotationRate()
|
smart.mineRotationRate()
|
||||||
smart.mineTemperatures()
|
smart.mineTemperatures()
|
||||||
smart.minePowerCycleCount()
|
smart.minePowerCycleCount()
|
||||||
|
smart.mineDeviceSCTStatus()
|
||||||
smart.mineDeviceStatistics()
|
smart.mineDeviceStatistics()
|
||||||
smart.mineNvmeSmartHealthInformationLog()
|
smart.mineNvmeSmartHealthInformationLog()
|
||||||
smart.mineNvmeSmartStatus()
|
smart.mineNvmeSmartStatus()
|
||||||
|
smart.mineDeviceStatus()
|
||||||
|
smart.mineDeviceErrorLog()
|
||||||
|
smart.mineDeviceSelfTestLog()
|
||||||
|
smart.mineDeviceERC()
|
||||||
|
smart.minePercentageUsed()
|
||||||
|
smart.mineAvailableSpare()
|
||||||
|
smart.mineAvailableSpareThreshold()
|
||||||
|
smart.mineCriticalWarning()
|
||||||
|
smart.mineMediaErrors()
|
||||||
|
smart.mineNumErrLogEntries()
|
||||||
|
smart.mineBytesRead()
|
||||||
|
smart.mineBytesWritten()
|
||||||
|
smart.mineSmartStatus()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart *SMARTctl) mineExitStatus() {
|
func (smart *SMARTctl) mineExitStatus() {
|
||||||
|
@ -232,6 +247,131 @@ func (smart *SMARTctl) minePowerCycleCount() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineDeviceSCTStatus() {
|
||||||
|
status := smart.json.Get("ata_sct_status")
|
||||||
|
if status.Exists() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceState,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
status.Get("device_state").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) minePercentageUsed() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDevicePercentageUsed,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
smart.json.Get("nvme_smart_health_information_log.percentage_used").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineAvailableSpare() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceAvailableSpare,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
smart.json.Get("nvme_smart_health_information_log.available_spare").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineAvailableSpareThreshold() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceAvailableSpareThreshold,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
smart.json.Get("nvme_smart_health_information_log.available_spare_threshold").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineCriticalWarning() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceCriticalWarning,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
smart.json.Get("nvme_smart_health_information_log.critical_warning").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineMediaErrors() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceMediaErrors,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
smart.json.Get("nvme_smart_health_information_log.media_errors").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineNumErrLogEntries() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceNumErrLogEntries,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
smart.json.Get("nvme_smart_health_information_log.num_err_log_entries").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineBytesRead() {
|
||||||
|
blockSize := smart.json.Get("logical_block_size").Float() * 1024
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceBytesRead,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
smart.json.Get("nvme_smart_health_information_log.data_units_read").Float()*blockSize,
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineBytesWritten() {
|
||||||
|
blockSize := smart.json.Get("logical_block_size").Float() * 1024
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceBytesWritten,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
smart.json.Get("nvme_smart_health_information_log.data_units_written").Float()*blockSize,
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineSmartStatus() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceSmartStatus,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
smart.json.Get("smart_status.passed").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (smart *SMARTctl) mineDeviceStatistics() {
|
func (smart *SMARTctl) mineDeviceStatistics() {
|
||||||
for _, page := range smart.json.Get("ata_device_statistics.pages").Array() {
|
for _, page := range smart.json.Get("ata_device_statistics.pages").Array() {
|
||||||
table := strings.TrimSpace(page.Get("name").String())
|
table := strings.TrimSpace(page.Get("name").String())
|
||||||
|
@ -256,6 +396,22 @@ func (smart *SMARTctl) mineDeviceStatistics() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, statistic := range smart.json.Get("sata_phy_event_counters.table").Array() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceStatistics,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
statistic.Get("value").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
"SATA PHY Event Counters",
|
||||||
|
strings.TrimSpace(statistic.Get("name").String()),
|
||||||
|
"V---",
|
||||||
|
"valid",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart *SMARTctl) mineLongFlags(json gjson.Result, flags []string) string {
|
func (smart *SMARTctl) mineLongFlags(json gjson.Result, flags []string) string {
|
||||||
|
@ -300,6 +456,19 @@ func (smart *SMARTctl) mineNvmeSmartHealthInformationLog() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineDeviceStatus() {
|
||||||
|
status := smart.json.Get("smart_status")
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceStatus,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
status.Get("passed").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (smart *SMARTctl) mineNvmeSmartStatus() {
|
func (smart *SMARTctl) mineNvmeSmartStatus() {
|
||||||
iStatus := smart.json.Get("smart_status")
|
iStatus := smart.json.Get("smart_status")
|
||||||
smart.ch <- prometheus.MustNewConstMetric(
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
@ -312,3 +481,58 @@ func (smart *SMARTctl) mineNvmeSmartStatus() {
|
||||||
smart.device.serial,
|
smart.device.serial,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineDeviceErrorLog() {
|
||||||
|
for logType, status := range smart.json.Get("ata_smart_error_log").Map() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceErrorLogCount,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
status.Get("count").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
logType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineDeviceSelfTestLog() {
|
||||||
|
for logType, status := range smart.json.Get("ata_smart_self_test_log").Map() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceSelfTestLogCount,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
status.Get("count").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
logType,
|
||||||
|
)
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceSelfTestLogErrorCount,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
status.Get("error_count_total").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
logType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctl) mineDeviceERC() {
|
||||||
|
for ercType, status := range smart.json.Get("ata_sct_erc").Map() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceERCSeconds,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
status.Get("deciseconds").Float()/10.0,
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
ercType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2
smartctl_exporter.yaml
Normal file → Executable file
2
smartctl_exporter.yaml
Normal file → Executable file
|
@ -1,5 +1,5 @@
|
||||||
smartctl_exporter:
|
smartctl_exporter:
|
||||||
bind_to: "[::1]:9633"
|
bind_to: "0.0.0.0:9633"
|
||||||
url_path: "/metrics"
|
url_path: "/metrics"
|
||||||
fake_json: no
|
fake_json: no
|
||||||
smartctl_location: /usr/sbin/smartctl
|
smartctl_location: /usr/sbin/smartctl
|
||||||
|
|
Loading…
Reference in a new issue