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 (
2024-10-18 09:18:48 +02:00
"log/slog"
2019-08-14 22:34:49 +02:00
"net/http"
2022-10-03 11:16:00 +02:00
"os"
2024-03-08 15:39:33 +01:00
"strings"
2023-08-07 13:23:41 +02:00
"sync"
2022-10-03 11:16:00 +02:00
"time"
2019-08-14 22:34:49 +02:00
2023-04-12 09:35:03 +02:00
kingpin "github.com/alecthomas/kingpin/v2"
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"
2024-10-18 09:18:48 +02:00
"github.com/prometheus/common/promslog"
"github.com/prometheus/common/promslog/flag"
2022-10-03 11:16:00 +02:00
"github.com/prometheus/common/version"
"github.com/prometheus/exporter-toolkit/web"
webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
2019-08-14 22:34:49 +02:00
)
2024-03-08 15:39:33 +01:00
// Device
type Device struct {
2024-12-19 11:02:09 +01:00
Name string
Type string
Label string
}
func ( d Device ) String ( ) string {
return d . Name + ";" + d . Type + " (" + d . Label + ")"
2024-03-08 15:39:33 +01:00
}
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
2024-03-08 15:39:33 +01:00
Devices [ ] Device
2022-10-03 11:16:00 +02:00
2024-10-18 09:18:48 +02:00
logger * slog . Logger
2023-08-07 13:23:41 +02:00
mutex sync . Mutex
2019-08-14 22:34:49 +02:00
}
// Describe sends the super-set of all possible descriptors of metrics
2023-08-07 13:23:41 +02:00
func ( i * SMARTctlManagerCollector ) Describe ( ch chan <- * prometheus . Desc ) {
2019-08-14 22:34:49 +02:00
prometheus . DescribeByCollect ( i , ch )
}
// Collect is called by the Prometheus registry when collecting metrics.
2023-08-07 13:23:41 +02:00
func ( i * SMARTctlManagerCollector ) Collect ( ch chan <- prometheus . Metric ) {
2019-08-15 23:01:16 +02:00
info := NewSMARTctlInfo ( ch )
2023-08-07 13:23:41 +02:00
i . mutex . Lock ( )
2022-10-03 11:16:00 +02:00
for _ , device := range i . Devices {
2022-11-04 19:42:36 +01:00
json := readData ( i . logger , device )
if json . Exists ( ) {
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 ( )
}
2019-08-14 22:34:49 +02:00
}
2023-06-28 10:28:11 +02:00
ch <- prometheus . MustNewConstMetric (
metricDeviceCount ,
prometheus . GaugeValue ,
float64 ( len ( i . Devices ) ) ,
)
2019-08-15 23:01:16 +02:00
info . Collect ( )
2023-08-07 13:23:41 +02:00
i . mutex . Unlock ( )
}
func ( i * SMARTctlManagerCollector ) RescanForDevices ( ) {
for {
time . Sleep ( * smartctlRescanInterval )
2024-10-18 09:18:48 +02:00
i . logger . Info ( "Rescanning for devices" )
2023-08-07 13:23:41 +02:00
devices := scanDevices ( i . logger )
2024-12-19 11:02:09 +01:00
devices = buildDevicesFromFlag ( devices )
2023-08-07 13:23:41 +02:00
i . mutex . Lock ( )
i . Devices = devices
i . mutex . Unlock ( )
}
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" ,
2023-01-31 13:35:39 +01:00
"The interval between smartctl polls" ,
2022-10-03 11:16:00 +02:00
) . Default ( "60s" ) . Duration ( )
2023-08-07 13:23:41 +02:00
smartctlRescanInterval = kingpin . Flag ( "smartctl.rescan" ,
"The interval between rescanning for new/disappeared devices. If the interval is smaller than 1s no rescanning takes place. If any devices are configured with smartctl.device also no rescanning takes place." ,
) . Default ( "10m" ) . Duration ( )
2024-12-19 11:02:09 +01:00
smartctlScan = kingpin . Flag ( "smartctl.scan" , "Enable scanning. This is a default if no devices are specified" ) . Default ( "false" ) . Bool ( )
2022-10-03 11:16:00 +02:00
smartctlDevices = kingpin . Flag ( "smartctl.device" ,
2024-12-19 11:02:09 +01:00
"The device to monitor. Device type can be specified after a semicolon, eg. '/dev/bus/0;megaraid,1' (repeatable)" ,
2022-10-03 11:16:00 +02:00
) . Strings ( )
2022-11-30 09:08:42 +01:00
smartctlDeviceExclude = kingpin . Flag (
"smartctl.device-exclude" ,
"Regexp of devices to exclude from automatic scanning. (mutually exclusive to device-include)" ,
) . Default ( "" ) . String ( )
smartctlDeviceInclude = kingpin . Flag (
"smartctl.device-include" ,
"Regexp of devices to exclude from automatic scanning. (mutually exclusive to device-exclude)" ,
) . Default ( "" ) . String ( )
2024-12-19 11:02:09 +01:00
smartctlScanDeviceTypes = kingpin . Flag (
"smartctl.scan-device-type" ,
"Device type to use during automatic scan. Special by-id value forces predictable device names. (repeatable)" ,
) . Strings ( )
2022-10-03 11:16:00 +02:00
smartctlFakeData = kingpin . Flag ( "smartctl.fake-data" ,
"The device to monitor (repeatable)" ,
) . Default ( "false" ) . Hidden ( ) . Bool ( )
)
2022-11-30 09:08:42 +01:00
// scanDevices uses smartctl to gather the list of available devices.
2024-10-18 09:18:48 +02:00
func scanDevices ( logger * slog . Logger ) [ ] Device {
2022-11-30 09:08:42 +01:00
filter := newDeviceFilter ( * smartctlDeviceExclude , * smartctlDeviceInclude )
json := readSMARTctlDevices ( logger )
scanDevices := json . Get ( "devices" ) . Array ( )
2024-03-08 15:39:33 +01:00
var scanDeviceResult [ ] Device
2022-11-30 09:08:42 +01:00
for _ , d := range scanDevices {
2024-12-19 11:02:09 +01:00
deviceName := d . Get ( "name" ) . String ( )
deviceType := d . Get ( "type" ) . String ( )
// SATA devices are reported as SCSI during scan - fallback to auto scraping
if deviceType == "scsi" {
deviceType = "auto"
}
deviceLabel := buildDeviceLabel ( deviceName , deviceType )
if filter . ignored ( deviceLabel ) {
logger . Info ( "Ignoring device" , "name" , deviceLabel )
2022-11-30 09:08:42 +01:00
} else {
2024-12-19 11:02:09 +01:00
logger . Info ( "Found device" , "name" , deviceLabel )
2024-03-08 15:39:33 +01:00
device := Device {
2024-12-19 11:02:09 +01:00
Name : deviceName ,
Type : deviceType ,
Label : deviceLabel ,
2024-03-08 15:39:33 +01:00
}
scanDeviceResult = append ( scanDeviceResult , device )
2022-11-30 09:08:42 +01:00
}
}
return scanDeviceResult
}
2024-12-19 11:02:09 +01:00
func buildDevicesFromFlag ( devices [ ] Device ) [ ] Device {
// TODO: deduplication?
for _ , device := range * smartctlDevices {
deviceName , deviceType , _ := strings . Cut ( device , ";" )
if deviceType == "" {
deviceType = "auto"
2024-03-08 15:39:33 +01:00
}
2024-12-19 11:02:09 +01:00
devices = append ( devices , Device {
Name : deviceName ,
Type : deviceType ,
Label : buildDeviceLabel ( deviceName , deviceType ) ,
} )
2024-03-08 15:39:33 +01:00
}
2024-12-19 11:02:09 +01:00
return devices
2024-03-08 15:39:33 +01:00
}
2022-10-03 11:16:00 +02:00
func main ( ) {
metricsPath := kingpin . Flag (
"web.telemetry-path" , "Path under which to expose metrics" ,
) . Default ( "/metrics" ) . String ( )
2022-10-22 20:00:18 +02:00
toolkitFlags := webflag . AddFlags ( kingpin . CommandLine , ":9633" )
2022-10-03 11:16:00 +02:00
2024-10-18 09:18:48 +02:00
promslogConfig := & promslog . Config { }
flag . AddFlags ( kingpin . CommandLine , promslogConfig )
2022-10-03 11:16:00 +02:00
kingpin . Version ( version . Print ( "smartctl_exporter" ) )
kingpin . HelpFlag . Short ( 'h' )
kingpin . Parse ( )
2024-10-18 09:18:48 +02:00
logger := promslog . New ( promslogConfig )
2022-10-03 11:16:00 +02:00
2024-10-18 09:18:48 +02:00
logger . Info ( "Starting smartctl_exporter" , "version" , version . Info ( ) )
logger . Info ( "Build context" , "build_context" , version . BuildContext ( ) )
2020-10-06 13:05:00 +02:00
2024-03-08 15:39:33 +01:00
var devices [ ] Device
2024-12-19 11:02:09 +01:00
if len ( * smartctlDevices ) == 0 {
* smartctlScan = true
}
if * smartctlScan {
devices = scanDevices ( logger )
logger . Info ( "Number of devices found" , "count" , len ( devices ) )
}
2022-11-30 09:08:42 +01:00
if len ( * smartctlDevices ) > 0 {
2024-10-18 09:18:48 +02:00
logger . Info ( "Devices specified" , "devices" , strings . Join ( * smartctlDevices , ", " ) )
2024-12-19 11:02:09 +01:00
devices = buildDevicesFromFlag ( devices )
2024-10-18 09:18:48 +02:00
logger . Info ( "Devices filtered" , "count" , len ( devices ) )
2022-10-03 11:16:00 +02:00
}
collector := SMARTctlManagerCollector {
Devices : devices ,
logger : logger ,
}
2024-12-19 11:02:09 +01:00
if * smartctlScan && * smartctlRescanInterval >= 1 * time . Second {
2024-10-18 09:18:48 +02:00
logger . Info ( "Start background scan process" )
logger . Info ( "Rescanning for devices every" , "rescanInterval" , * smartctlRescanInterval )
2023-08-07 13:23:41 +02:00
go collector . RescanForDevices ( )
}
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
)
2023-08-07 13:23:41 +02:00
prometheus . WrapRegistererWithPrefix ( "" , reg ) . MustRegister ( & collector )
2019-08-14 22:34:49 +02:00
2022-10-03 11:16:00 +02:00
http . Handle ( * metricsPath , promhttp . HandlerFor ( reg , promhttp . HandlerOpts { } ) )
2023-04-12 09:35:03 +02:00
if * metricsPath != "/" && * metricsPath != "" {
landingConfig := web . LandingConfig {
Name : "smartctl_exporter" ,
Description : "Prometheus Exporter for S.M.A.R.T. devices" ,
Version : version . Info ( ) ,
Links : [ ] web . LandingLinks {
{
Address : * metricsPath ,
Text : "Metrics" ,
} ,
} ,
}
landingPage , err := web . NewLandingPage ( landingConfig )
2022-10-03 11:16:00 +02:00
if err != nil {
2024-10-18 09:18:48 +02:00
logger . Error ( "error creating landing page" , "err" , err )
2023-04-12 09:35:03 +02:00
os . Exit ( 1 )
2022-10-03 11:16:00 +02:00
}
2023-04-12 09:35:03 +02:00
http . Handle ( "/" , landingPage )
}
2022-10-03 11:16:00 +02:00
2022-10-22 20:00:18 +02:00
srv := & http . Server { }
if err := web . ListenAndServe ( srv , toolkitFlags , logger ) ; err != nil {
2024-10-18 09:18:48 +02:00
logger . Error ( "error running HTTP server" , "err" , err )
2022-10-03 11:16:00 +02:00
os . Exit ( 1 )
}
2019-08-14 22:34:49 +02:00
}