diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1cca7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor +bin +*.json diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c303615 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 18c4062..e95f079 100644 --- a/README.md +++ b/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 +``` diff --git a/gjsonext.go b/gjsonext.go new file mode 100644 index 0000000..7005430 --- /dev/null +++ b/gjsonext.go @@ -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 +} diff --git a/logging.go b/logging.go new file mode 100644 index 0000000..d2e9e94 --- /dev/null +++ b/logging.go @@ -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...) + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..44e2a12 --- /dev/null +++ b/main.go @@ -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)) +} diff --git a/metrics.go b/metrics.go new file mode 100644 index 0000000..c8fbd1f --- /dev/null +++ b/metrics.go @@ -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, + ) +) diff --git a/options.go b/options.go new file mode 100644 index 0000000..f02ab3d --- /dev/null +++ b/options.go @@ -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 +} diff --git a/readjson.go b/readjson.go new file mode 100644 index 0000000..67d90c5 --- /dev/null +++ b/readjson.go @@ -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) +} diff --git a/smartctl.go b/smartctl.go new file mode 100644 index 0000000..17a5276 --- /dev/null +++ b/smartctl.go @@ -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, + ) +} diff --git a/smartctl_exporter.yaml b/smartctl_exporter.yaml new file mode 100644 index 0000000..939a02b --- /dev/null +++ b/smartctl_exporter.yaml @@ -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