mirror of
https://github.com/prometheus-community/smartctl_exporter.git
synced 2024-11-23 01:43:07 +01:00
First commit
This commit is contained in:
parent
f1e1e3fe23
commit
9e6e240e85
11 changed files with 744 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
vendor
|
||||||
|
bin
|
||||||
|
*.json
|
42
Makefile
Normal file
42
Makefile
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
GOPATH=$(shell pwd)/vendor:$(shell pwd)
|
||||||
|
GOBIN=$(shell pwd)/bin
|
||||||
|
GOFILES=$(wildcard *.go)
|
||||||
|
GONAME=$(shell basename "$(PWD)")
|
||||||
|
PID=/tmp/go-$(GONAME).pid
|
||||||
|
|
||||||
|
build: get
|
||||||
|
@echo "Building $(GOFILES) to ./bin"
|
||||||
|
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go build -o bin/$(GONAME) $(GOFILES)
|
||||||
|
|
||||||
|
get:
|
||||||
|
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go get .
|
||||||
|
|
||||||
|
install:
|
||||||
|
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go install $(GOFILES)
|
||||||
|
|
||||||
|
run: build
|
||||||
|
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go run $(GOFILES) --config=$(shell pwd)/smartctl_exporter.yaml --debug --verbose
|
||||||
|
|
||||||
|
run-sudo: build
|
||||||
|
sudo bin/$(GONAME) --config=$(shell pwd)/smartctl_exporter.yaml --debug --verbose
|
||||||
|
|
||||||
|
watch:
|
||||||
|
@$(MAKE) restart &
|
||||||
|
@fswatch -o . -e 'bin/.*' | xargs -n1 -I{} make restart
|
||||||
|
|
||||||
|
restart: clear stop clean build start
|
||||||
|
|
||||||
|
start: build
|
||||||
|
@echo "Starting bin/$(GONAME)"
|
||||||
|
@./bin/$(GONAME) & echo $$! > $(PID)
|
||||||
|
|
||||||
|
stop:
|
||||||
|
@echo "Stopping bin/$(GONAME) if it's running"
|
||||||
|
@-kill `[[ -f $(PID) ]] && cat $(PID)` 2>/dev/null || true
|
||||||
|
|
||||||
|
clear:
|
||||||
|
@clear
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo "Cleaning"
|
||||||
|
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go clean
|
74
README.md
74
README.md
|
@ -1,2 +1,76 @@
|
||||||
# smartctl_exporter
|
# smartctl_exporter
|
||||||
Export smartctl statistics to prometheus
|
Export smartctl statistics to prometheus
|
||||||
|
|
||||||
|
## Need more?
|
||||||
|
**If you need additional metrics - contact me :)**
|
||||||
|
**Create a feature request, describe the metric that you would like to have and attach exported from smartctl json file**
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
## Command line options
|
||||||
|
* `--config=/path/to/file.yaml`: Path to configuration file, defaulr `/etc/smartctl_exporter.yaml`
|
||||||
|
* `--verbose`: verbosed log, default no
|
||||||
|
* `--debug`: Debug logging, default no
|
||||||
|
* `--version`: Show version and exit
|
||||||
|
|
||||||
|
## Configuration file
|
||||||
|
Example content:
|
||||||
|
```
|
||||||
|
smartctl_exporter:
|
||||||
|
bind_to: "[::1]:9631"
|
||||||
|
url_path: "/metrics"
|
||||||
|
fake_json: no
|
||||||
|
smartctl_location: /usr/sbin/smartctl
|
||||||
|
devices:
|
||||||
|
- /dev/sda
|
||||||
|
- /dev/sdb
|
||||||
|
- /dev/sdc
|
||||||
|
- /dev/sdd
|
||||||
|
- /dev/sde
|
||||||
|
- /dev/sdf
|
||||||
|
```
|
||||||
|
`fake_json` used for debugging.
|
||||||
|
|
||||||
|
# Example metrics
|
||||||
|
```
|
||||||
|
# HELP smartctl_device Device info
|
||||||
|
# TYPE smartctl_device gauge
|
||||||
|
smartctl_device{ata_additional_product_id="HC7020E0",ata_version="ATA8-ACS, ATA/ATAPI-7 T13/1532D revision 4a",device="/dev/sda",firmware_version="1.03",interface="sat",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",protocol="ATA",sata_version="SATA 3.1",serial_number="P02448109994"} 1.0
|
||||||
|
smartctl_device{ata_additional_product_id="unknown",ata_version="ACS-2 (minor revision not indicated)",device="/dev/sdc",firmware_version="82.00A82",interface="sat",model_family="Western Digital Red",model_name="WDC WD20EFRX-68EUZN0",protocol="ATA",sata_version="SATA 3.0",serial_number="WD-WCC4M4VX3C69"} 1.0
|
||||||
|
# HELP smartctl_device_attribute Device attributes
|
||||||
|
# TYPE smartctl_device_attribute gauge
|
||||||
|
smartctl_device_attribute{device="/dev/sda",flags="-O----",id="9",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",name="Power_On_Hours",serial_number="P02448109994",value_type="raw"} 2952.0
|
||||||
|
smartctl_device_attribute{device="/dev/sda",flags="-O----",id="9",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",name="Power_On_Hours",serial_number="P02448109994",value_type="thresh"} 0.0
|
||||||
|
smartctl_device_attribute{device="/dev/sda",flags="-O----",id="9",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",name="Power_On_Hours",serial_number="P02448109994",value_type="value"} 100.0
|
||||||
|
smartctl_device_attribute{device="/dev/sda",flags="-O----",id="9",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",name="Power_On_Hours",serial_number="P02448109994",value_type="worst"} 100.0
|
||||||
|
# HELP smartctl_device_block_size Device block size
|
||||||
|
# TYPE smartctl_device_block_size gauge
|
||||||
|
smartctl_device_block_size{blocks_type="logical",device="/dev/sda",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",serial_number="P02448109994"} 512.0
|
||||||
|
smartctl_device_block_size{blocks_type="logical",device="/dev/sdb",model_family="SandForce Driven SSDs",model_name="OCZ-VERTEX3",serial_number="A20Y8011312000361"} 512.0
|
||||||
|
smartctl_device_block_size{blocks_type="logical",device="/dev/sdc",model_family="Western Digital Red",model_name="WDC WD20EFRX-68EUZN0",serial_number="WD-WCC4M4VX3C69"} 512.0
|
||||||
|
smartctl_device_block_size{blocks_type="logical",device="/dev/sdd",model_family="Western Digital Red",model_name="WDC WD20EFRX-68EUZN0",serial_number="WD-WCC4M6DCPPC7"} 512.0
|
||||||
|
# HELP smartctl_device_capacity_blocks Device capacity in blocks
|
||||||
|
# TYPE smartctl_device_capacity_blocks gauge
|
||||||
|
smartctl_device_capacity_blocks{device="/dev/sda",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",serial_number="P02448109994"} 2.5006968e+08
|
||||||
|
smartctl_device_capacity_blocks{device="/dev/sdb",model_family="SandForce Driven SSDs",model_name="OCZ-VERTEX3",serial_number="A20Y8011312000361"} 2.34441648e+08
|
||||||
|
smartctl_device_capacity_blocks{device="/dev/sdc",model_family="Western Digital Red",model_name="WDC WD20EFRX-68EUZN0",serial_number="WD-WCC4M4VX3C69"} 3.907029168e+09
|
||||||
|
# HELP smartctl_device_capacity_bytes Device capacity in bytes
|
||||||
|
# TYPE smartctl_device_capacity_bytes gauge
|
||||||
|
smartctl_device_capacity_bytes{device="/dev/sda",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",serial_number="P02448109994"} 1.2803567616e+11
|
||||||
|
smartctl_device_capacity_bytes{device="/dev/sdb",model_family="SandForce Driven SSDs",model_name="OCZ-VERTEX3",serial_number="A20Y8011312000361"} 1.20034123776e+11
|
||||||
|
smartctl_device_capacity_bytes{device="/dev/sdc",model_family="Western Digital Red",model_name="WDC WD20EFRX-68EUZN0",serial_number="WD-WCC4M4VX3C69"} 2.000398934016e+12
|
||||||
|
# HELP smartctl_device_interface_speed Device interface speed, bits per second
|
||||||
|
# TYPE smartctl_device_interface_speed gauge
|
||||||
|
smartctl_device_interface_speed{device="/dev/sda",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",serial_number="P02448109994",speed_type="current"} 6e+09
|
||||||
|
smartctl_device_interface_speed{device="/dev/sda",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",serial_number="P02448109994",speed_type="max"} 6e+09
|
||||||
|
smartctl_device_interface_speed{device="/dev/sdb",model_family="SandForce Driven SSDs",model_name="OCZ-VERTEX3",serial_number="A20Y8011312000361",speed_type="current"} 6e+09
|
||||||
|
# HELP smartctl_device_power_on_seconds Device power on seconds
|
||||||
|
# TYPE smartctl_device_power_on_seconds counter
|
||||||
|
smartctl_device_power_on_seconds{device="/dev/sda",model_family="Plextor M3/M5/M6 Series SSDs",model_name="PLEXTOR PX-128M6S",serial_number="P02448109994"} 1.06272e+07
|
||||||
|
smartctl_device_power_on_seconds{device="/dev/sdb",model_family="SandForce Driven SSDs",model_name="OCZ-VERTEX3",serial_number="A20Y8011312000361"} 1.0568592e+08
|
||||||
|
smartctl_device_power_on_seconds{device="/dev/sdc",model_family="Western Digital Red",model_name="WDC WD20EFRX-68EUZN0",serial_number="WD-WCC4M4VX3C69"} 6.68232e+07
|
||||||
|
# HELP smartctl_device_temperature Device temperature celsius
|
||||||
|
# TYPE smartctl_device_temperature gauge
|
||||||
|
smartctl_device_temperature{device="/dev/sdb",model_family="SandForce Driven SSDs",model_name="OCZ-VERTEX3",serial_number="A20Y8011312000361",temperature_type="current"} 30.0
|
||||||
|
smartctl_device_temperature{device="/dev/sdb",model_family="SandForce Driven SSDs",model_name="OCZ-VERTEX3",serial_number="A20Y8011312000361",temperature_type="lifetime_max"} 30.0
|
||||||
|
smartctl_device_temperature{device="/dev/sdb",model_family="SandForce Driven SSDs",model_name="OCZ-VERTEX3",serial_number="A20Y8011312000361",temperature_type="lifetime_min"} 30.0
|
||||||
|
```
|
||||||
|
|
23
gjsonext.go
Normal file
23
gjsonext.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetStringIfExists returns json value or default
|
||||||
|
func GetStringIfExists(json gjson.Result, key string, def string) string {
|
||||||
|
value := json.Get(key)
|
||||||
|
if value.Exists() {
|
||||||
|
return value.String()
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloatIfExists returns json value or default
|
||||||
|
func GetFloatIfExists(json gjson.Result, key string, def float64) float64 {
|
||||||
|
value := json.Get(key)
|
||||||
|
if value.Exists() {
|
||||||
|
return value.Float()
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
55
logging.go
Normal file
55
logging.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
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...)
|
||||||
|
}
|
||||||
|
}
|
54
main.go
Normal file
54
main.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
options Options
|
||||||
|
logger Logger
|
||||||
|
)
|
||||||
|
|
||||||
|
// SMARTctlManagerCollector implements the Collector interface.
|
||||||
|
type SMARTctlManagerCollector struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe sends the super-set of all possible descriptors of metrics
|
||||||
|
func (i SMARTctlManagerCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||||
|
prometheus.DescribeByCollect(i, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect is called by the Prometheus registry when collecting metrics.
|
||||||
|
func (i SMARTctlManagerCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
for _, device := range options.SMARTctl.Devices {
|
||||||
|
json, err := readData(device)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed parsing SMARTctl data: %s", err)
|
||||||
|
} else {
|
||||||
|
smart := NewSMARTctl(json, ch)
|
||||||
|
smart.Collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
options = loadOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
reg := prometheus.NewPedanticRegistry()
|
||||||
|
reg.MustRegister(
|
||||||
|
prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}),
|
||||||
|
prometheus.NewGoCollector(),
|
||||||
|
)
|
||||||
|
|
||||||
|
prometheus.WrapRegistererWithPrefix("", reg).MustRegister(SMARTctlManagerCollector{})
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
142
metrics.go
Normal file
142
metrics.go
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
metricSmartctlVersion = prometheus.NewDesc(
|
||||||
|
"smartctl_version",
|
||||||
|
"smartctl version",
|
||||||
|
[]string{
|
||||||
|
"json_format_version",
|
||||||
|
"smartctl_version",
|
||||||
|
"svn_revision",
|
||||||
|
"build_info",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceModel = prometheus.NewDesc(
|
||||||
|
"smartctl_device",
|
||||||
|
"Device info",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"interface",
|
||||||
|
"protocol",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
"ata_additional_product_id",
|
||||||
|
"firmware_version",
|
||||||
|
"ata_version",
|
||||||
|
"sata_version",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceCapacityBlocks = prometheus.NewDesc(
|
||||||
|
"smartctl_device_capacity_blocks",
|
||||||
|
"Device capacity in blocks",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceCapacityBytes = prometheus.NewDesc(
|
||||||
|
"smartctl_device_capacity_bytes",
|
||||||
|
"Device capacity in bytes",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceBlockSize = prometheus.NewDesc(
|
||||||
|
"smartctl_device_block_size",
|
||||||
|
"Device block size",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
"blocks_type",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceInterfaceSpeed = prometheus.NewDesc(
|
||||||
|
"smartctl_device_interface_speed",
|
||||||
|
"Device interface speed, bits per second",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
"speed_type",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceAttribute = prometheus.NewDesc(
|
||||||
|
"smartctl_device_attribute",
|
||||||
|
"Device attributes",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
"name",
|
||||||
|
"flags",
|
||||||
|
"value_type",
|
||||||
|
"id",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDevicePowerOnSeconds = prometheus.NewDesc(
|
||||||
|
"smartctl_device_power_on_seconds",
|
||||||
|
"Device power on seconds",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceRotationRate = prometheus.NewDesc(
|
||||||
|
"smartctl_device_rotation_rate",
|
||||||
|
"Device rotation rate",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDeviceTemperature = prometheus.NewDesc(
|
||||||
|
"smartctl_device_temperature",
|
||||||
|
"Device temperature celsius",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
"temperature_type",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
metricDevicePowerCycleCount = prometheus.NewDesc(
|
||||||
|
"smartctl_device_power_cycle_count",
|
||||||
|
"Device power cycle count",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
)
|
66
options.go
Normal file
66
options.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
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"`
|
||||||
|
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: "9631",
|
||||||
|
URLPath: "/metrics",
|
||||||
|
FakeJSON: false,
|
||||||
|
SMARTctlLocation: "/usr/sbin/smartctl",
|
||||||
|
Devices: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if yaml.Unmarshal(yamlFile, &opts) != nil {
|
||||||
|
logger.Panic("Failed parse %s: %s", configFile, err)
|
||||||
|
}
|
||||||
|
logger.Debug("Parsed options: %s", opts)
|
||||||
|
return opts
|
||||||
|
}
|
50
readjson.go
Normal file
50
readjson.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse json to gjson object
|
||||||
|
func parseJSON(data string) (gjson.Result, error) {
|
||||||
|
if !gjson.Valid(data) {
|
||||||
|
return gjson.Parse("{}"), errors.New("Invalid JSON")
|
||||||
|
}
|
||||||
|
return gjson.Parse(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading fake smartctl json
|
||||||
|
func readFakeSMARTctl(device string) (gjson.Result, error) {
|
||||||
|
splitted := strings.Split(device, "/")
|
||||||
|
filename := fmt.Sprintf("%s.json", splitted[len(splitted)-1])
|
||||||
|
logger.Verbose("Read fake S.M.A.R.T. data from json: %s", filename)
|
||||||
|
jsonFile, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Fake S.M.A.R.T. data reading error: %s", err)
|
||||||
|
return parseJSON("{}")
|
||||||
|
}
|
||||||
|
return parseJSON(string(jsonFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get json from smartctl and parse it
|
||||||
|
func readSMARTctl(device string) (gjson.Result, error) {
|
||||||
|
logger.Debug("Collecting S.M.A.R.T. counters, device: %s...", device)
|
||||||
|
out, err := exec.Command(options.SMARTctl.SMARTctlLocation, "--json", "--xall", device).Output()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("S.M.A.R.T. output reading error: %s", err)
|
||||||
|
}
|
||||||
|
return parseJSON(string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select json source and parse
|
||||||
|
func readData(device string) (gjson.Result, error) {
|
||||||
|
if options.SMARTctl.FakeJSON {
|
||||||
|
return readFakeSMARTctl(device)
|
||||||
|
}
|
||||||
|
return readSMARTctl(device)
|
||||||
|
}
|
223
smartctl.go
Normal file
223
smartctl.go
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SMARTDevice - short info about device
|
||||||
|
type SMARTDevice struct {
|
||||||
|
device string
|
||||||
|
serial string
|
||||||
|
family string
|
||||||
|
model string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMARTctl object
|
||||||
|
type SMARTctl struct {
|
||||||
|
ch chan<- prometheus.Metric
|
||||||
|
json gjson.Result
|
||||||
|
device SMARTDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSMARTctl is NFTables 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()),
|
||||||
|
}
|
||||||
|
logger.Verbose("Collecting metrics from %s: %s, %s", smart.device.device, smart.device.family, smart.device.model)
|
||||||
|
return smart
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect metrics
|
||||||
|
func (smart SMARTctl) Collect() {
|
||||||
|
// smart.mineVersion()
|
||||||
|
smart.mineDevice()
|
||||||
|
smart.mineCapacity()
|
||||||
|
smart.mineInterfaceSpeed()
|
||||||
|
smart.mineDeviceAttribute()
|
||||||
|
smart.minePowerOnSeconds()
|
||||||
|
smart.mineRotationRate()
|
||||||
|
smart.mineTemperatures()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart SMARTctl) mineVersion() {
|
||||||
|
jsonVersion := smart.json.Get("json_format_version").Array()
|
||||||
|
smartctlJSON := smart.json.Get("smartctl")
|
||||||
|
smartctlVersion := smartctlJSON.Get("version").Array()
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricSmartctlVersion,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
1,
|
||||||
|
fmt.Sprintf("%d.%d", jsonVersion[0].Int(), jsonVersion[1].Int()),
|
||||||
|
fmt.Sprintf("%d.%d", smartctlVersion[0].Int(), smartctlVersion[1].Int()),
|
||||||
|
smartctlJSON.Get("svn_revision").String(),
|
||||||
|
smartctlJSON.Get("build_info").String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart SMARTctl) mineDevice() {
|
||||||
|
device := smart.json.Get("device")
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceModel,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
1,
|
||||||
|
smart.device.device,
|
||||||
|
device.Get("type").String(),
|
||||||
|
device.Get("protocol").String(),
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
GetStringIfExists(smart.json, "ata_additional_product_id", "unknown"),
|
||||||
|
smart.json.Get("firmware_version").String(),
|
||||||
|
smart.json.Get("ata_version.string").String(),
|
||||||
|
smart.json.Get("sata_version.string").String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart SMARTctl) mineCapacity() {
|
||||||
|
capacity := smart.json.Get("user_capacity")
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceCapacityBlocks,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
capacity.Get("blocks").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceCapacityBytes,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
capacity.Get("bytes").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
for _, blockType := range []string{"logical", "physical"} {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceBlockSize,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
smart.json.Get(fmt.Sprintf("%s_block_size", blockType)).Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
blockType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart SMARTctl) mineInterfaceSpeed() {
|
||||||
|
iSpeed := smart.json.Get("interface_speed")
|
||||||
|
for _, speedType := range []string{"max", "current"} {
|
||||||
|
tSpeed := iSpeed.Get(speedType)
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceInterfaceSpeed,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
tSpeed.Get("units_per_second").Float()*tSpeed.Get("bits_per_unit").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
speedType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart SMARTctl) mineDeviceAttribute() {
|
||||||
|
for _, attribute := range smart.json.Get("ata_smart_attributes.table").Array() {
|
||||||
|
name := strings.TrimSpace(attribute.Get("name").String())
|
||||||
|
flags := strings.TrimSpace(attribute.Get("flags.string").String())
|
||||||
|
id := attribute.Get("id").String()
|
||||||
|
for key, path := range map[string]string{
|
||||||
|
"value": "value",
|
||||||
|
"worst": "worst",
|
||||||
|
"thresh": "thresh",
|
||||||
|
"raw": "raw.value",
|
||||||
|
} {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceAttribute,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
attribute.Get(path).Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
name,
|
||||||
|
flags,
|
||||||
|
key,
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart SMARTctl) minePowerOnSeconds() {
|
||||||
|
pot := smart.json.Get("power_on_time")
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDevicePowerOnSeconds,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
GetFloatIfExists(pot, "hours", 0)*60*60+GetFloatIfExists(pot, "minutes", 0)*60,
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart SMARTctl) mineRotationRate() {
|
||||||
|
rRate := GetFloatIfExists(smart.json, "rotation_rate", 0)
|
||||||
|
if rRate > 0 {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceRotationRate,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
rRate,
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart SMARTctl) mineTemperatures() {
|
||||||
|
temperatures := smart.json.Get("temperature")
|
||||||
|
if temperatures.Exists() {
|
||||||
|
temperatures.ForEach(func(key, value gjson.Result) bool {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDeviceTemperature,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
value.Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
key.String(),
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart SMARTctl) minePowerCycleCount() {
|
||||||
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
metricDevicePowerCycleCount,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
smart.json.Get("power_cycle_count").Float(),
|
||||||
|
smart.device.device,
|
||||||
|
smart.device.family,
|
||||||
|
smart.device.model,
|
||||||
|
smart.device.serial,
|
||||||
|
)
|
||||||
|
}
|
12
smartctl_exporter.yaml
Normal file
12
smartctl_exporter.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
smartctl_exporter:
|
||||||
|
bind_to: "[::1]:9631"
|
||||||
|
url_path: "/metrics"
|
||||||
|
fake_json: no
|
||||||
|
smartctl_location: /usr/sbin/smartctl
|
||||||
|
devices:
|
||||||
|
- /dev/sda
|
||||||
|
- /dev/sdb
|
||||||
|
- /dev/sdc
|
||||||
|
- /dev/sdd
|
||||||
|
- /dev/sde
|
||||||
|
- /dev/sdf
|
Loading…
Reference in a new issue