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"
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"
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"
2019-08-14 22:34:49 +02:00
)
2024-03-08 15:39:33 +01:00
// Device
type Device struct {
Name string ` json:"name" `
Info_Name string ` json:"info_name" `
Type string ` json:"type" `
}
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
logger log . 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 )
level . Info ( i . logger ) . Log ( "msg" , "Rescanning for devices" )
devices := scanDevices ( i . logger )
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 ( )
2022-10-03 11:16:00 +02:00
smartctlDevices = kingpin . Flag ( "smartctl.device" ,
"The device to monitor (repeatable)" ,
) . 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 ( )
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-03-08 15:39:33 +01:00
func scanDevices ( logger log . 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-03-08 15:39:33 +01:00
deviceName := extractDiskName ( strings . TrimSpace ( d . Get ( "info_name" ) . String ( ) ) )
2022-11-30 09:08:42 +01:00
if filter . ignored ( deviceName ) {
level . Info ( logger ) . Log ( "msg" , "Ignoring device" , "name" , deviceName )
} else {
level . Info ( logger ) . Log ( "msg" , "Found device" , "name" , deviceName )
2024-03-08 15:39:33 +01:00
device := Device {
Name : d . Get ( "name" ) . String ( ) ,
Info_Name : deviceName ,
Type : d . Get ( "type" ) . String ( ) ,
}
scanDeviceResult = append ( scanDeviceResult , device )
2022-11-30 09:08:42 +01:00
}
}
return scanDeviceResult
}
2024-03-08 15:39:33 +01:00
func filterDevices ( logger log . Logger , devices [ ] Device , filters [ ] string ) [ ] Device {
var filtered [ ] Device
for _ , d := range devices {
for _ , filter := range filters {
level . Debug ( logger ) . Log ( "msg" , "filterDevices" , "device" , d . Info_Name , "filter" , filter )
if strings . Contains ( d . Info_Name , filter ) {
filtered = append ( filtered , d )
break
}
}
}
return filtered
}
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
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
2024-03-08 15:39:33 +01:00
var devices [ ] Device
devices = scanDevices ( logger )
level . Info ( logger ) . Log ( "msg" , "Number of devices found" , "count" , len ( devices ) )
2022-11-30 09:08:42 +01:00
if len ( * smartctlDevices ) > 0 {
2024-03-08 15:39:33 +01:00
level . Info ( logger ) . Log ( "msg" , "Devices specified" , "devices" , strings . Join ( * smartctlDevices , ", " ) )
devices = filterDevices ( logger , devices , * smartctlDevices )
level . Info ( logger ) . Log ( "msg" , "Devices filtered" , "count" , len ( devices ) )
2022-10-03 11:16:00 +02:00
}
collector := SMARTctlManagerCollector {
Devices : devices ,
logger : logger ,
}
2024-03-08 15:39:33 +01:00
if * smartctlRescanInterval >= 1 * time . Second {
2023-08-07 13:23:41 +02:00
level . Info ( logger ) . Log ( "msg" , "Start background scan process" )
level . Info ( logger ) . Log ( "msg" , "Rescanning for devices every" , "rescanInterval" , * smartctlRescanInterval )
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 {
2023-04-12 09:35:03 +02:00
level . Error ( logger ) . Log ( "err" , err )
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 {
2022-10-03 11:16:00 +02:00
level . Error ( logger ) . Log ( "err" , err )
os . Exit ( 1 )
}
2019-08-14 22:34:49 +02:00
}