From beb765eb1a996e806118822e5ed614072e9e51c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D1=80=D0=BB=D0=BE=D0=B2=20=D0=9C=D0=B0=D0=BA?= =?UTF-8?q?=D1=81=D0=B8=D0=BC?= Date: Fri, 16 Aug 2019 00:01:16 +0300 Subject: [PATCH] option for set minimum time period between run smartctl; smartctl info metric --- main.go | 13 +++++------ metrics.go | 11 +++++++++ options.go | 22 +++++++++++++----- readjson.go | 45 ++++++++++++++++++++++++++++--------- smartctl.go | 38 +++++++++++++++---------------- smartctl_exporter.yaml | 1 + smartctlinfo.go | 51 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 139 insertions(+), 42 deletions(-) create mode 100644 smartctlinfo.go diff --git a/main.go b/main.go index 44e2a12..4c769b4 100644 --- a/main.go +++ b/main.go @@ -24,15 +24,14 @@ func (i SMARTctlManagerCollector) Describe(ch chan<- *prometheus.Desc) { // Collect is called by the Prometheus registry when collecting metrics. func (i SMARTctlManagerCollector) Collect(ch chan<- prometheus.Metric) { + info := NewSMARTctlInfo(ch) 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() - } + json := readData(device) + info.SetJSON(json) + smart := NewSMARTctl(json, ch) + smart.Collect() } + info.Collect() } func init() { diff --git a/metrics.go b/metrics.go index c8fbd1f..50671b2 100644 --- a/metrics.go +++ b/metrics.go @@ -139,4 +139,15 @@ var ( }, nil, ) + metricDeviceExitStatus = prometheus.NewDesc( + "smartctl_device_smartctl_exit_status", + "Exit status of smartctl on device", + []string{ + "device", + "model_family", + "model_name", + "serial_number", + }, + nil, + ) ) diff --git a/options.go b/options.go index 2a36d8b..059f65b 100644 --- a/options.go +++ b/options.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "os" + "time" yaml "gopkg.in/yaml.v2" ) @@ -15,11 +16,13 @@ var ( // 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"` + BindTo string `yaml:"bind_to"` + URLPath string `yaml:"url_path"` + FakeJSON bool `yaml:"fake_json"` + SMARTctlLocation string `yaml:"smartctl_location"` + CollectPeriod string `yaml:"collect_not_more_than_period"` + CollectPeriodDuration time.Duration + Devices []string `yaml:"devices"` } // Options is a representation of a options @@ -54,6 +57,7 @@ func loadOptions() Options { URLPath: "/metrics", FakeJSON: false, SMARTctlLocation: "/usr/sbin/smartctl", + CollectPeriod: "60s", Devices: []string{}, }, } @@ -61,6 +65,14 @@ func loadOptions() Options { if yaml.Unmarshal(yamlFile, &opts) != nil { 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) return opts } diff --git a/readjson.go b/readjson.go index 67d90c5..39141e1 100644 --- a/readjson.go +++ b/readjson.go @@ -1,25 +1,39 @@ package main import ( - "errors" "fmt" "io/ioutil" "os/exec" "strings" + "time" "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 -func parseJSON(data string) (gjson.Result, error) { +func parseJSON(data string) gjson.Result { 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 -func readFakeSMARTctl(device string) (gjson.Result, error) { +func readFakeSMARTctl(device string) gjson.Result { 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) @@ -32,19 +46,30 @@ func readFakeSMARTctl(device string) (gjson.Result, error) { } // 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) +func readSMARTctl(device string) gjson.Result { + 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) + logger.Warning("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) { +func readData(device string) gjson.Result { if options.SMARTctl.FakeJSON { 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 } diff --git a/smartctl.go b/smartctl.go index 9173e3f..03ea9af 100644 --- a/smartctl.go +++ b/smartctl.go @@ -39,8 +39,8 @@ func NewSMARTctl(json gjson.Result, ch chan<- prometheus.Metric) SMARTctl { } // Collect metrics -func (smart SMARTctl) Collect() { - // smart.mineVersion() +func (smart *SMARTctl) Collect() { + smart.mineExitStatus() smart.mineDevice() smart.mineCapacity() smart.mineInterfaceSpeed() @@ -48,24 +48,22 @@ func (smart SMARTctl) Collect() { smart.minePowerOnSeconds() smart.mineRotationRate() smart.mineTemperatures() + smart.minePowerCycleCount() } -func (smart SMARTctl) mineVersion() { - jsonVersion := smart.json.Get("json_format_version").Array() - smartctlJSON := smart.json.Get("smartctl") - smartctlVersion := smartctlJSON.Get("version").Array() +func (smart *SMARTctl) mineExitStatus() { smart.ch <- prometheus.MustNewConstMetric( - metricSmartctlVersion, + metricDeviceExitStatus, 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(), + smart.json.Get("smartctl.exit_status").Float(), + smart.device.device, + smart.device.family, + smart.device.model, + smart.device.serial, ) } -func (smart SMARTctl) mineDevice() { +func (smart *SMARTctl) mineDevice() { device := smart.json.Get("device") smart.ch <- prometheus.MustNewConstMetric( metricDeviceModel, @@ -84,7 +82,7 @@ func (smart SMARTctl) mineDevice() { ) } -func (smart SMARTctl) mineCapacity() { +func (smart *SMARTctl) mineCapacity() { capacity := smart.json.Get("user_capacity") smart.ch <- prometheus.MustNewConstMetric( metricDeviceCapacityBlocks, @@ -118,7 +116,7 @@ func (smart SMARTctl) mineCapacity() { } } -func (smart SMARTctl) mineInterfaceSpeed() { +func (smart *SMARTctl) mineInterfaceSpeed() { iSpeed := smart.json.Get("interface_speed") for _, speedType := range []string{"max", "current"} { 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() { name := strings.TrimSpace(attribute.Get("name").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") smart.ch <- prometheus.MustNewConstMetric( metricDevicePowerOnSeconds, @@ -176,7 +174,7 @@ func (smart SMARTctl) minePowerOnSeconds() { ) } -func (smart SMARTctl) mineRotationRate() { +func (smart *SMARTctl) mineRotationRate() { rRate := GetFloatIfExists(smart.json, "rotation_rate", 0) if rRate > 0 { 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") if temperatures.Exists() { 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( metricDevicePowerCycleCount, prometheus.CounterValue, diff --git a/smartctl_exporter.yaml b/smartctl_exporter.yaml index ca1c62d..7740083 100644 --- a/smartctl_exporter.yaml +++ b/smartctl_exporter.yaml @@ -3,6 +3,7 @@ smartctl_exporter: url_path: "/metrics" fake_json: no smartctl_location: /usr/sbin/smartctl + collect_not_more_than_period: 20s devices: - /dev/sda - /dev/sdb diff --git a/smartctlinfo.go b/smartctlinfo.go new file mode 100644 index 0000000..5b0159a --- /dev/null +++ b/smartctlinfo.go @@ -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(), + ) +}