2022-08-05 03:37:13 +02:00
|
|
|
// Copyright 2022 The Prometheus Authors
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2019-08-14 22:34:49 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
2022-10-03 11:16:00 +02:00
|
|
|
"os"
|
|
|
|
"time"
|
2019-08-14 22:34:49 +02:00
|
|
|
|
2022-10-03 11:16:00 +02:00
|
|
|
"github.com/go-kit/log"
|
|
|
|
"github.com/go-kit/log/level"
|
2019-08-14 22:34:49 +02:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2022-06-17 13:27:10 +02:00
|
|
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
2019-08-14 22:34:49 +02:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2022-10-03 11:16:00 +02:00
|
|
|
"github.com/prometheus/common/promlog"
|
|
|
|
"github.com/prometheus/common/promlog/flag"
|
|
|
|
"github.com/prometheus/common/version"
|
|
|
|
"github.com/prometheus/exporter-toolkit/web"
|
|
|
|
webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
|
|
|
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
2019-08-14 22:34:49 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// SMARTctlManagerCollector implements the Collector interface.
|
|
|
|
type SMARTctlManagerCollector struct {
|
2022-10-03 11:16:00 +02:00
|
|
|
CollectPeriod string
|
|
|
|
CollectPeriodDuration time.Duration
|
|
|
|
Devices []string
|
|
|
|
|
|
|
|
logger log.Logger
|
2019-08-14 22:34:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2019-08-15 23:01:16 +02:00
|
|
|
info := NewSMARTctlInfo(ch)
|
2022-10-03 11:16:00 +02:00
|
|
|
for _, device := range i.Devices {
|
|
|
|
if json, err := readData(i.logger, device); err == nil {
|
2020-10-29 22:35:49 +01:00
|
|
|
info.SetJSON(json)
|
2022-10-03 11:16:00 +02:00
|
|
|
smart := NewSMARTctl(i.logger, json, ch)
|
2020-10-29 22:35:49 +01:00
|
|
|
smart.Collect()
|
|
|
|
} else {
|
2022-10-03 11:16:00 +02:00
|
|
|
level.Error(i.logger).Log("msg", "Error collecting SMART data", "err", err.Error())
|
2020-10-29 22:35:49 +01:00
|
|
|
}
|
2019-08-14 22:34:49 +02:00
|
|
|
}
|
2019-08-15 23:01:16 +02:00
|
|
|
info.Collect()
|
2019-08-14 22:34:49 +02:00
|
|
|
}
|
|
|
|
|
2022-10-03 11:16:00 +02:00
|
|
|
var (
|
|
|
|
smartctlPath = kingpin.Flag("smartctl.path",
|
|
|
|
"The path to the smartctl binary",
|
|
|
|
).Default("/usr/sbin/smartctl").String()
|
|
|
|
smartctlInterval = kingpin.Flag("smartctl.interval",
|
|
|
|
"The interval between smarctl polls",
|
|
|
|
).Default("60s").Duration()
|
|
|
|
smartctlDevices = kingpin.Flag("smartctl.device",
|
|
|
|
"The device to monitor (repeatable)",
|
|
|
|
).Strings()
|
|
|
|
smartctlFakeData = kingpin.Flag("smartctl.fake-data",
|
|
|
|
"The device to monitor (repeatable)",
|
|
|
|
).Default("false").Hidden().Bool()
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
listenAddress := kingpin.Flag("web.listen-address",
|
|
|
|
"Address to listen on for web interface and telemetry",
|
|
|
|
).Default(":9633").String()
|
|
|
|
metricsPath := kingpin.Flag(
|
|
|
|
"web.telemetry-path", "Path under which to expose metrics",
|
|
|
|
).Default("/metrics").String()
|
|
|
|
webConfig := webflag.AddFlags(kingpin.CommandLine)
|
|
|
|
|
|
|
|
promlogConfig := &promlog.Config{}
|
|
|
|
flag.AddFlags(kingpin.CommandLine, promlogConfig)
|
|
|
|
kingpin.Version(version.Print("smartctl_exporter"))
|
|
|
|
kingpin.HelpFlag.Short('h')
|
|
|
|
kingpin.Parse()
|
|
|
|
logger := promlog.New(promlogConfig)
|
|
|
|
|
2022-10-14 12:48:26 +02:00
|
|
|
level.Info(logger).Log("msg", "Starting smartctl_exporter", "version", version.Info())
|
2022-10-03 11:16:00 +02:00
|
|
|
level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext())
|
2020-10-06 13:05:00 +02:00
|
|
|
|
2022-10-15 05:39:36 +02:00
|
|
|
// Scan the host devices
|
|
|
|
json := readSMARTctlDevices(logger)
|
|
|
|
scanDevices := json.Get("devices").Array()
|
|
|
|
scanDevicesSet := make(map[string]bool)
|
|
|
|
var scanDeviceNames []string
|
|
|
|
for _, d := range scanDevices {
|
|
|
|
deviceName := d.Get("name").String()
|
|
|
|
level.Debug(logger).Log("msg", "Found device", "name", deviceName)
|
|
|
|
scanDevicesSet[deviceName] = true
|
|
|
|
scanDeviceNames = append(scanDeviceNames, deviceName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the configuration and verify that it is available
|
2022-10-03 11:16:00 +02:00
|
|
|
devices := *smartctlDevices
|
2022-10-15 05:39:36 +02:00
|
|
|
var readDeviceNames []string
|
|
|
|
for _, device := range devices {
|
|
|
|
if _, ok := scanDevicesSet[device]; ok {
|
|
|
|
readDeviceNames = append(readDeviceNames, device)
|
|
|
|
} else {
|
|
|
|
level.Warn(logger).Log("msg", "Device unavailable", "name", device)
|
|
|
|
}
|
|
|
|
}
|
2022-10-03 11:16:00 +02:00
|
|
|
|
2022-10-15 05:39:36 +02:00
|
|
|
if len(readDeviceNames) > 0 {
|
|
|
|
devices = readDeviceNames
|
|
|
|
} else {
|
2022-10-03 11:16:00 +02:00
|
|
|
level.Info(logger).Log("msg", "No devices specified, trying to load them automatically")
|
2022-10-15 05:39:36 +02:00
|
|
|
devices = scanDeviceNames
|
2020-10-06 13:05:00 +02:00
|
|
|
}
|
2019-08-14 22:34:49 +02:00
|
|
|
|
2022-10-03 11:16:00 +02:00
|
|
|
if len(devices) == 0 {
|
|
|
|
level.Error(logger).Log("msg", "No devices found")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
collector := SMARTctlManagerCollector{
|
|
|
|
Devices: devices,
|
|
|
|
logger: logger,
|
|
|
|
}
|
|
|
|
|
2019-08-14 22:34:49 +02:00
|
|
|
reg := prometheus.NewPedanticRegistry()
|
|
|
|
reg.MustRegister(
|
2022-06-17 13:27:10 +02:00
|
|
|
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
|
|
|
collectors.NewGoCollector(),
|
2019-08-14 22:34:49 +02:00
|
|
|
)
|
|
|
|
|
2022-10-03 11:16:00 +02:00
|
|
|
prometheus.WrapRegistererWithPrefix("", reg).MustRegister(collector)
|
2019-08-14 22:34:49 +02:00
|
|
|
|
2022-10-03 11:16:00 +02:00
|
|
|
level.Info(logger).Log("msg", "Listening on", "address", *listenAddress)
|
|
|
|
http.Handle(*metricsPath, promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
_, err := w.Write([]byte(`<html>
|
|
|
|
<head><title>Smartctl Exporter</title></head>
|
|
|
|
<body>
|
|
|
|
<h1>Smartctl Exporter</h1>
|
|
|
|
<p><a href="` + *metricsPath + `">Metrics</a></p>
|
|
|
|
</body>
|
|
|
|
</html>`))
|
|
|
|
if err != nil {
|
|
|
|
level.Error(logger).Log("msg", "Couldn't write response", "err", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
srv := &http.Server{Addr: *listenAddress}
|
|
|
|
if err := web.ListenAndServe(srv, *webConfig, logger); err != nil {
|
|
|
|
level.Error(logger).Log("err", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2019-08-14 22:34:49 +02:00
|
|
|
}
|