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"
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
)
// 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
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
}
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.
func scanDevices ( logger log . Logger ) [ ] string {
filter := newDeviceFilter ( * smartctlDeviceExclude , * smartctlDeviceInclude )
json := readSMARTctlDevices ( logger )
scanDevices := json . Get ( "devices" ) . Array ( )
var scanDeviceResult [ ] string
for _ , d := range scanDevices {
deviceName := d . Get ( "name" ) . String ( )
if filter . ignored ( deviceName ) {
level . Info ( logger ) . Log ( "msg" , "Ignoring device" , "name" , deviceName )
} else {
level . Info ( logger ) . Log ( "msg" , "Found device" , "name" , deviceName )
scanDeviceResult = append ( scanDeviceResult , deviceName )
}
}
return scanDeviceResult
}
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
2022-11-30 09:08:42 +01:00
var devices [ ] string
if len ( * smartctlDevices ) > 0 {
devices = * smartctlDevices
2022-10-15 05:39:36 +02:00
} else {
2022-10-03 11:16:00 +02:00
level . Info ( logger ) . Log ( "msg" , "No devices specified, trying to load them automatically" )
2022-11-30 09:08:42 +01:00
devices = scanDevices ( logger )
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 ,
}
2023-08-07 13:23:41 +02:00
if * smartctlRescanInterval >= 1 * time . Second && len ( * smartctlDevices ) == 0 {
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
}