mirror of
https://github.com/prometheus-community/smartctl_exporter.git
synced 2024-12-21 02:21:55 +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
|
||||
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