mirror of
https://github.com/prometheus-community/smartctl_exporter.git
synced 2024-11-23 01:43:07 +01:00
option for set minimum time period between run smartctl; smartctl info metric
This commit is contained in:
parent
de13d91241
commit
beb765eb1a
7 changed files with 139 additions and 42 deletions
9
main.go
9
main.go
|
@ -24,15 +24,14 @@ func (i SMARTctlManagerCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||||
|
|
||||||
// Collect is called by the Prometheus registry when collecting metrics.
|
// Collect is called by the Prometheus registry when collecting metrics.
|
||||||
func (i SMARTctlManagerCollector) Collect(ch chan<- prometheus.Metric) {
|
func (i SMARTctlManagerCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
info := NewSMARTctlInfo(ch)
|
||||||
for _, device := range options.SMARTctl.Devices {
|
for _, device := range options.SMARTctl.Devices {
|
||||||
json, err := readData(device)
|
json := readData(device)
|
||||||
if err != nil {
|
info.SetJSON(json)
|
||||||
logger.Error("Failed parsing SMARTctl data: %s", err)
|
|
||||||
} else {
|
|
||||||
smart := NewSMARTctl(json, ch)
|
smart := NewSMARTctl(json, ch)
|
||||||
smart.Collect()
|
smart.Collect()
|
||||||
}
|
}
|
||||||
}
|
info.Collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
11
metrics.go
11
metrics.go
|
@ -139,4 +139,15 @@ var (
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
metricDeviceExitStatus = prometheus.NewDesc(
|
||||||
|
"smartctl_device_smartctl_exit_status",
|
||||||
|
"Exit status of smartctl on device",
|
||||||
|
[]string{
|
||||||
|
"device",
|
||||||
|
"model_family",
|
||||||
|
"model_name",
|
||||||
|
"serial_number",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
12
options.go
12
options.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
@ -19,6 +20,8 @@ type SMARTOptions struct {
|
||||||
URLPath string `yaml:"url_path"`
|
URLPath string `yaml:"url_path"`
|
||||||
FakeJSON bool `yaml:"fake_json"`
|
FakeJSON bool `yaml:"fake_json"`
|
||||||
SMARTctlLocation string `yaml:"smartctl_location"`
|
SMARTctlLocation string `yaml:"smartctl_location"`
|
||||||
|
CollectPeriod string `yaml:"collect_not_more_than_period"`
|
||||||
|
CollectPeriodDuration time.Duration
|
||||||
Devices []string `yaml:"devices"`
|
Devices []string `yaml:"devices"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +57,7 @@ func loadOptions() Options {
|
||||||
URLPath: "/metrics",
|
URLPath: "/metrics",
|
||||||
FakeJSON: false,
|
FakeJSON: false,
|
||||||
SMARTctlLocation: "/usr/sbin/smartctl",
|
SMARTctlLocation: "/usr/sbin/smartctl",
|
||||||
|
CollectPeriod: "60s",
|
||||||
Devices: []string{},
|
Devices: []string{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -61,6 +65,14 @@ func loadOptions() Options {
|
||||||
if yaml.Unmarshal(yamlFile, &opts) != nil {
|
if yaml.Unmarshal(yamlFile, &opts) != nil {
|
||||||
logger.Panic("Failed parse %s: %s", configFile, err)
|
logger.Panic("Failed parse %s: %s", configFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d, err := time.ParseDuration(opts.SMARTctl.CollectPeriod)
|
||||||
|
if err != nil {
|
||||||
|
logger.Panic("Failed read collect_not_more_than_period (%s): %s", opts.SMARTctl.CollectPeriod, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.SMARTctl.CollectPeriodDuration = d
|
||||||
|
|
||||||
logger.Debug("Parsed options: %s", opts)
|
logger.Debug("Parsed options: %s", opts)
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
45
readjson.go
45
readjson.go
|
@ -1,25 +1,39 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// JSONCache caching json
|
||||||
|
type JSONCache struct {
|
||||||
|
JSON gjson.Result
|
||||||
|
LastCollect time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
jsonCache map[string]JSONCache
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
jsonCache = make(map[string]JSONCache)
|
||||||
|
}
|
||||||
|
|
||||||
// Parse json to gjson object
|
// Parse json to gjson object
|
||||||
func parseJSON(data string) (gjson.Result, error) {
|
func parseJSON(data string) gjson.Result {
|
||||||
if !gjson.Valid(data) {
|
if !gjson.Valid(data) {
|
||||||
return gjson.Parse("{}"), errors.New("Invalid JSON")
|
return gjson.Parse("{}")
|
||||||
}
|
}
|
||||||
return gjson.Parse(data), nil
|
return gjson.Parse(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reading fake smartctl json
|
// Reading fake smartctl json
|
||||||
func readFakeSMARTctl(device string) (gjson.Result, error) {
|
func readFakeSMARTctl(device string) gjson.Result {
|
||||||
splitted := strings.Split(device, "/")
|
splitted := strings.Split(device, "/")
|
||||||
filename := fmt.Sprintf("%s.json", splitted[len(splitted)-1])
|
filename := fmt.Sprintf("%s.json", splitted[len(splitted)-1])
|
||||||
logger.Verbose("Read fake S.M.A.R.T. data from json: %s", filename)
|
logger.Verbose("Read fake S.M.A.R.T. data from json: %s", filename)
|
||||||
|
@ -32,19 +46,30 @@ func readFakeSMARTctl(device string) (gjson.Result, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get json from smartctl and parse it
|
// Get json from smartctl and parse it
|
||||||
func readSMARTctl(device string) (gjson.Result, error) {
|
func readSMARTctl(device string) gjson.Result {
|
||||||
logger.Debug("Collecting S.M.A.R.T. counters, device: %s...", device)
|
logger.Debug("Collecting S.M.A.R.T. counters, device: %s", device)
|
||||||
out, err := exec.Command(options.SMARTctl.SMARTctlLocation, "--json", "--xall", device).Output()
|
out, err := exec.Command(options.SMARTctl.SMARTctlLocation, "--json", "--xall", device).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("S.M.A.R.T. output reading error: %s", err)
|
logger.Warning("S.M.A.R.T. output reading error: %s", err)
|
||||||
}
|
}
|
||||||
return parseJSON(string(out))
|
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 {
|
||||||
if options.SMARTctl.FakeJSON {
|
if options.SMARTctl.FakeJSON {
|
||||||
return readFakeSMARTctl(device)
|
return readFakeSMARTctl(device)
|
||||||
}
|
}
|
||||||
return readSMARTctl(device)
|
|
||||||
|
if value, ok := jsonCache[device]; ok {
|
||||||
|
// logger.Debug("Cache exists")
|
||||||
|
if time.Now().After(value.LastCollect.Add(options.SMARTctl.CollectPeriodDuration)) {
|
||||||
|
// logger.Debug("Cache update")
|
||||||
|
jsonCache[device] = JSONCache{JSON: readSMARTctl(device), LastCollect: time.Now()}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// logger.Debug("Cache not exists")
|
||||||
|
jsonCache[device] = JSONCache{JSON: readSMARTctl(device), LastCollect: time.Now()}
|
||||||
|
}
|
||||||
|
return jsonCache[device].JSON
|
||||||
}
|
}
|
||||||
|
|
38
smartctl.go
38
smartctl.go
|
@ -39,8 +39,8 @@ func NewSMARTctl(json gjson.Result, ch chan<- prometheus.Metric) SMARTctl {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect metrics
|
// Collect metrics
|
||||||
func (smart SMARTctl) Collect() {
|
func (smart *SMARTctl) Collect() {
|
||||||
// smart.mineVersion()
|
smart.mineExitStatus()
|
||||||
smart.mineDevice()
|
smart.mineDevice()
|
||||||
smart.mineCapacity()
|
smart.mineCapacity()
|
||||||
smart.mineInterfaceSpeed()
|
smart.mineInterfaceSpeed()
|
||||||
|
@ -48,24 +48,22 @@ func (smart SMARTctl) Collect() {
|
||||||
smart.minePowerOnSeconds()
|
smart.minePowerOnSeconds()
|
||||||
smart.mineRotationRate()
|
smart.mineRotationRate()
|
||||||
smart.mineTemperatures()
|
smart.mineTemperatures()
|
||||||
|
smart.minePowerCycleCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart SMARTctl) mineVersion() {
|
func (smart *SMARTctl) mineExitStatus() {
|
||||||
jsonVersion := smart.json.Get("json_format_version").Array()
|
|
||||||
smartctlJSON := smart.json.Get("smartctl")
|
|
||||||
smartctlVersion := smartctlJSON.Get("version").Array()
|
|
||||||
smart.ch <- prometheus.MustNewConstMetric(
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
metricSmartctlVersion,
|
metricDeviceExitStatus,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
1,
|
smart.json.Get("smartctl.exit_status").Float(),
|
||||||
fmt.Sprintf("%d.%d", jsonVersion[0].Int(), jsonVersion[1].Int()),
|
smart.device.device,
|
||||||
fmt.Sprintf("%d.%d", smartctlVersion[0].Int(), smartctlVersion[1].Int()),
|
smart.device.family,
|
||||||
smartctlJSON.Get("svn_revision").String(),
|
smart.device.model,
|
||||||
smartctlJSON.Get("build_info").String(),
|
smart.device.serial,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart SMARTctl) mineDevice() {
|
func (smart *SMARTctl) mineDevice() {
|
||||||
device := smart.json.Get("device")
|
device := smart.json.Get("device")
|
||||||
smart.ch <- prometheus.MustNewConstMetric(
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
metricDeviceModel,
|
metricDeviceModel,
|
||||||
|
@ -84,7 +82,7 @@ func (smart SMARTctl) mineDevice() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart SMARTctl) mineCapacity() {
|
func (smart *SMARTctl) mineCapacity() {
|
||||||
capacity := smart.json.Get("user_capacity")
|
capacity := smart.json.Get("user_capacity")
|
||||||
smart.ch <- prometheus.MustNewConstMetric(
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
metricDeviceCapacityBlocks,
|
metricDeviceCapacityBlocks,
|
||||||
|
@ -118,7 +116,7 @@ func (smart SMARTctl) mineCapacity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart SMARTctl) mineInterfaceSpeed() {
|
func (smart *SMARTctl) mineInterfaceSpeed() {
|
||||||
iSpeed := smart.json.Get("interface_speed")
|
iSpeed := smart.json.Get("interface_speed")
|
||||||
for _, speedType := range []string{"max", "current"} {
|
for _, speedType := range []string{"max", "current"} {
|
||||||
tSpeed := iSpeed.Get(speedType)
|
tSpeed := iSpeed.Get(speedType)
|
||||||
|
@ -135,7 +133,7 @@ func (smart SMARTctl) mineInterfaceSpeed() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart SMARTctl) mineDeviceAttribute() {
|
func (smart *SMARTctl) mineDeviceAttribute() {
|
||||||
for _, attribute := range smart.json.Get("ata_smart_attributes.table").Array() {
|
for _, attribute := range smart.json.Get("ata_smart_attributes.table").Array() {
|
||||||
name := strings.TrimSpace(attribute.Get("name").String())
|
name := strings.TrimSpace(attribute.Get("name").String())
|
||||||
flags := strings.TrimSpace(attribute.Get("flags.string").String())
|
flags := strings.TrimSpace(attribute.Get("flags.string").String())
|
||||||
|
@ -163,7 +161,7 @@ func (smart SMARTctl) mineDeviceAttribute() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart SMARTctl) minePowerOnSeconds() {
|
func (smart *SMARTctl) minePowerOnSeconds() {
|
||||||
pot := smart.json.Get("power_on_time")
|
pot := smart.json.Get("power_on_time")
|
||||||
smart.ch <- prometheus.MustNewConstMetric(
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
metricDevicePowerOnSeconds,
|
metricDevicePowerOnSeconds,
|
||||||
|
@ -176,7 +174,7 @@ func (smart SMARTctl) minePowerOnSeconds() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart SMARTctl) mineRotationRate() {
|
func (smart *SMARTctl) mineRotationRate() {
|
||||||
rRate := GetFloatIfExists(smart.json, "rotation_rate", 0)
|
rRate := GetFloatIfExists(smart.json, "rotation_rate", 0)
|
||||||
if rRate > 0 {
|
if rRate > 0 {
|
||||||
smart.ch <- prometheus.MustNewConstMetric(
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
|
@ -191,7 +189,7 @@ func (smart SMARTctl) mineRotationRate() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart SMARTctl) mineTemperatures() {
|
func (smart *SMARTctl) mineTemperatures() {
|
||||||
temperatures := smart.json.Get("temperature")
|
temperatures := smart.json.Get("temperature")
|
||||||
if temperatures.Exists() {
|
if temperatures.Exists() {
|
||||||
temperatures.ForEach(func(key, value gjson.Result) bool {
|
temperatures.ForEach(func(key, value gjson.Result) bool {
|
||||||
|
@ -210,7 +208,7 @@ func (smart SMARTctl) mineTemperatures() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (smart SMARTctl) minePowerCycleCount() {
|
func (smart *SMARTctl) minePowerCycleCount() {
|
||||||
smart.ch <- prometheus.MustNewConstMetric(
|
smart.ch <- prometheus.MustNewConstMetric(
|
||||||
metricDevicePowerCycleCount,
|
metricDevicePowerCycleCount,
|
||||||
prometheus.CounterValue,
|
prometheus.CounterValue,
|
||||||
|
|
|
@ -3,6 +3,7 @@ smartctl_exporter:
|
||||||
url_path: "/metrics"
|
url_path: "/metrics"
|
||||||
fake_json: no
|
fake_json: no
|
||||||
smartctl_location: /usr/sbin/smartctl
|
smartctl_location: /usr/sbin/smartctl
|
||||||
|
collect_not_more_than_period: 20s
|
||||||
devices:
|
devices:
|
||||||
- /dev/sda
|
- /dev/sda
|
||||||
- /dev/sdb
|
- /dev/sdb
|
||||||
|
|
51
smartctlinfo.go
Normal file
51
smartctlinfo.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SMARTctlInfo object
|
||||||
|
type SMARTctlInfo struct {
|
||||||
|
ch chan<- prometheus.Metric
|
||||||
|
json gjson.Result
|
||||||
|
Ready bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSMARTctlInfo is smartctl constructor
|
||||||
|
func NewSMARTctlInfo(ch chan<- prometheus.Metric) SMARTctlInfo {
|
||||||
|
smart := SMARTctlInfo{}
|
||||||
|
smart.ch = ch
|
||||||
|
smart.Ready = false
|
||||||
|
return smart
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetJSON metrics
|
||||||
|
func (smart *SMARTctlInfo) SetJSON(json gjson.Result) {
|
||||||
|
if !smart.Ready {
|
||||||
|
smart.json = json
|
||||||
|
smart.Ready = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect metrics
|
||||||
|
func (smart *SMARTctlInfo) Collect() {
|
||||||
|
smart.mineVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smart *SMARTctlInfo) mineVersion() {
|
||||||
|
smartctlJSON := smart.json.Get("smartctl")
|
||||||
|
smartctlVersion := smartctlJSON.Get("version").Array()
|
||||||
|
jsonVersion := smart.json.Get("json_format_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(),
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue