2016-08-26 06:59:27 +00:00
package main
import (
"flag"
2016-09-01 14:04:43 +00:00
"fmt"
2016-09-27 12:37:12 +00:00
"io"
2016-08-26 06:59:27 +00:00
"net/http"
2016-09-01 14:04:43 +00:00
"os"
"sort"
"strings"
2016-08-26 06:59:27 +00:00
"sync"
2016-09-01 14:04:43 +00:00
"time"
2016-08-26 06:59:27 +00:00
2016-09-16 06:36:58 +00:00
"golang.org/x/sys/windows/svc"
2017-06-26 19:03:17 +00:00
"github.com/StackExchange/wmi"
2016-09-01 12:55:35 +00:00
"github.com/martinlindhe/wmi_exporter/collector"
2016-08-26 06:59:27 +00:00
"github.com/prometheus/client_golang/prometheus"
2017-04-30 22:12:05 +00:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2016-09-01 14:04:43 +00:00
"github.com/prometheus/common/log"
"github.com/prometheus/common/version"
2016-08-26 06:59:27 +00:00
)
2016-09-01 14:04:43 +00:00
// WmiCollector implements the prometheus.Collector interface.
type WmiCollector struct {
collectors map [ string ] collector . Collector
2016-08-26 06:59:27 +00:00
}
2016-09-01 14:04:43 +00:00
const (
2017-03-04 11:44:47 +00:00
defaultCollectors = "cpu,cs,logical_disk,net,os,service,system"
defaultCollectorsPlaceholder = "[defaults]"
serviceName = "wmi_exporter"
2016-09-01 14:04:43 +00:00
)
var (
2017-07-05 11:12:42 +00:00
scrapeDurationDesc = prometheus . NewDesc (
prometheus . BuildFQName ( collector . Namespace , "exporter" , "collector_duration_seconds" ) ,
"wmi_exporter: Duration of a collection." ,
[ ] string { "collector" } ,
nil ,
)
scrapeSuccessDesc = prometheus . NewDesc (
prometheus . BuildFQName ( collector . Namespace , "exporter" , "collector_success" ) ,
"wmi_exporter: Whether the collector was successful." ,
[ ] string { "collector" } ,
nil ,
2016-09-01 14:04:43 +00:00
)
)
2016-08-26 06:59:27 +00:00
// Describe sends all the descriptors of the collectors included to
// the provided channel.
2016-09-01 14:04:43 +00:00
func ( coll WmiCollector ) Describe ( ch chan <- * prometheus . Desc ) {
2017-07-05 11:12:42 +00:00
ch <- scrapeDurationDesc
ch <- scrapeSuccessDesc
2016-08-26 06:59:27 +00:00
}
// Collect sends the collected metrics from each of the collectors to
// prometheus. Collect could be called several times concurrently
// and thus its run is protected by a single mutex.
2016-09-01 14:04:43 +00:00
func ( coll WmiCollector ) Collect ( ch chan <- prometheus . Metric ) {
wg := sync . WaitGroup { }
wg . Add ( len ( coll . collectors ) )
for name , c := range coll . collectors {
go func ( name string , c collector . Collector ) {
execute ( name , c , ch )
wg . Done ( )
} ( name , c )
}
wg . Wait ( )
}
2016-08-26 06:59:27 +00:00
2016-09-01 14:04:43 +00:00
func filterAvailableCollectors ( collectors string ) string {
var availableCollectors [ ] string
for _ , c := range strings . Split ( collectors , "," ) {
_ , ok := collector . Factories [ c ]
if ok {
availableCollectors = append ( availableCollectors , c )
}
2016-08-26 06:59:27 +00:00
}
2016-09-01 14:04:43 +00:00
return strings . Join ( availableCollectors , "," )
}
func execute ( name string , c collector . Collector , ch chan <- prometheus . Metric ) {
begin := time . Now ( )
err := c . Collect ( ch )
duration := time . Since ( begin )
2017-07-05 11:12:42 +00:00
var success float64
2016-09-01 14:04:43 +00:00
if err != nil {
log . Errorf ( "ERROR: %s collector failed after %fs: %s" , name , duration . Seconds ( ) , err )
2017-07-05 11:12:42 +00:00
success = 0
2016-09-01 14:04:43 +00:00
} else {
log . Debugf ( "OK: %s collector succeeded after %fs." , name , duration . Seconds ( ) )
2017-07-05 11:12:42 +00:00
success = 1
2016-09-01 14:04:43 +00:00
}
2017-07-05 11:12:42 +00:00
ch <- prometheus . MustNewConstMetric (
scrapeDurationDesc ,
prometheus . GaugeValue ,
duration . Seconds ( ) ,
name ,
)
ch <- prometheus . MustNewConstMetric (
scrapeSuccessDesc ,
prometheus . GaugeValue ,
success ,
name ,
)
2016-09-01 14:04:43 +00:00
}
2017-03-04 11:44:47 +00:00
func expandEnabledCollectors ( enabled string ) [ ] string {
expanded := strings . Replace ( enabled , defaultCollectorsPlaceholder , defaultCollectors , - 1 )
separated := strings . Split ( expanded , "," )
unique := map [ string ] bool { }
for _ , s := range separated {
if s != "" {
unique [ s ] = true
}
}
result := make ( [ ] string , 0 , len ( unique ) )
for s , _ := range unique {
result = append ( result , s )
}
return result
}
2016-09-01 14:04:43 +00:00
func loadCollectors ( list string ) ( map [ string ] collector . Collector , error ) {
collectors := map [ string ] collector . Collector { }
2017-03-04 11:44:47 +00:00
enabled := expandEnabledCollectors ( list )
for _ , name := range enabled {
2016-09-01 14:04:43 +00:00
fn , ok := collector . Factories [ name ]
if ! ok {
return nil , fmt . Errorf ( "collector '%s' not available" , name )
}
c , err := fn ( )
if err != nil {
return nil , err
}
collectors [ name ] = c
}
return collectors , nil
}
func init ( ) {
prometheus . MustRegister ( version . NewCollector ( "wmi_exporter" ) )
2016-08-26 06:59:27 +00:00
}
2017-06-26 19:03:17 +00:00
func initWbem ( ) {
// This initialization prevents a memory leak on WMF 5+. See
// https://github.com/martinlindhe/wmi_exporter/issues/77 and linked issues
// for details.
log . Debugf ( "Initializing SWbemServices" )
s , err := wmi . InitializeSWbemServices ( wmi . DefaultClient )
if err != nil {
log . Fatal ( err )
}
wmi . DefaultClient . SWbemServicesClient = s
}
2017-07-18 11:18:36 +00:00
func usage ( ) {
fmt . Fprintf ( os . Stderr , "Usage of %s:\n" , os . Args [ 0 ] )
flag . PrintDefaults ( )
fmt . Fprintf ( os . Stderr , "\nNote: If executing from Powershell, the flags need to quoted. For example:\n%s\n" ,
"\twmi_exporter \"-collectors.enabled\" iis" )
}
2016-08-26 06:59:27 +00:00
func main ( ) {
var (
2016-09-01 14:04:43 +00:00
showVersion = flag . Bool ( "version" , false , "Print version information." )
listenAddress = flag . String ( "telemetry.addr" , ":9182" , "host:port for WMI exporter." )
metricsPath = flag . String ( "telemetry.path" , "/metrics" , "URL path for surfacing collected metrics." )
2017-03-04 11:44:47 +00:00
enabledCollectors = flag . String ( "collectors.enabled" , filterAvailableCollectors ( defaultCollectors ) , "Comma-separated list of collectors to use. Use '[default]' as a placeholder for all the collectors enabled by default" )
2016-09-01 14:04:43 +00:00
printCollectors = flag . Bool ( "collectors.print" , false , "If true, print available collectors and exit." )
2016-08-26 06:59:27 +00:00
)
2017-07-18 11:18:36 +00:00
flag . Usage = usage
2016-08-26 06:59:27 +00:00
flag . Parse ( )
2016-09-01 14:04:43 +00:00
if * showVersion {
fmt . Fprintln ( os . Stdout , version . Print ( "wmi_exporter" ) )
os . Exit ( 0 )
}
if * printCollectors {
collectorNames := make ( sort . StringSlice , 0 , len ( collector . Factories ) )
for n := range collector . Factories {
collectorNames = append ( collectorNames , n )
}
collectorNames . Sort ( )
fmt . Printf ( "Available collectors:\n" )
for _ , n := range collectorNames {
fmt . Printf ( " - %s\n" , n )
}
return
}
2017-06-26 19:03:17 +00:00
initWbem ( )
2016-09-16 06:36:58 +00:00
isInteractive , err := svc . IsAnInteractiveSession ( )
if err != nil {
log . Fatal ( err )
}
stopCh := make ( chan bool )
if ! isInteractive {
go svc . Run ( serviceName , & wmiExporterService { stopCh : stopCh } )
}
2016-09-01 14:04:43 +00:00
collectors , err := loadCollectors ( * enabledCollectors )
if err != nil {
log . Fatalf ( "Couldn't load collectors: %s" , err )
}
2016-09-16 06:36:58 +00:00
log . Infof ( "Enabled collectors: %v" , strings . Join ( keys ( collectors ) , ", " ) )
2016-09-01 14:04:43 +00:00
nodeCollector := WmiCollector { collectors : collectors }
prometheus . MustRegister ( nodeCollector )
2016-08-26 06:59:27 +00:00
2017-04-30 22:12:05 +00:00
http . Handle ( * metricsPath , promhttp . Handler ( ) )
2016-09-27 12:37:12 +00:00
http . HandleFunc ( "/health" , healthCheck )
2016-08-26 06:59:27 +00:00
http . HandleFunc ( "/" , func ( w http . ResponseWriter , r * http . Request ) {
http . Redirect ( w , r , * metricsPath , http . StatusMovedPermanently )
} )
2016-09-01 14:04:43 +00:00
log . Infoln ( "Starting WMI exporter" , version . Info ( ) )
log . Infoln ( "Build context" , version . BuildContext ( ) )
2016-09-16 06:36:58 +00:00
go func ( ) {
log . Infoln ( "Starting server on" , * listenAddress )
2017-04-30 22:12:05 +00:00
log . Fatalf ( "cannot start WMI exporter: %s" , http . ListenAndServe ( * listenAddress , nil ) )
2016-09-16 06:36:58 +00:00
} ( )
for {
if <- stopCh {
log . Info ( "Shutting down WMI exporter" )
break
}
}
}
2016-09-27 12:37:12 +00:00
func healthCheck ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
io . WriteString ( w , ` { "status":"ok"} ` )
}
2016-09-16 06:36:58 +00:00
func keys ( m map [ string ] collector . Collector ) [ ] string {
ret := make ( [ ] string , 0 , len ( m ) )
2016-09-27 12:37:12 +00:00
for key := range m {
2016-09-16 06:36:58 +00:00
ret = append ( ret , key )
}
return ret
}
type wmiExporterService struct {
stopCh chan <- bool
}
func ( s * wmiExporterService ) Execute ( args [ ] string , r <- chan svc . ChangeRequest , changes chan <- svc . Status ) ( ssec bool , errno uint32 ) {
const cmdsAccepted = svc . AcceptStop | svc . AcceptShutdown
changes <- svc . Status { State : svc . StartPending }
changes <- svc . Status { State : svc . Running , Accepts : cmdsAccepted }
loop :
for {
select {
case c := <- r :
switch c . Cmd {
case svc . Interrogate :
changes <- c . CurrentStatus
case svc . Stop , svc . Shutdown :
s . stopCh <- true
break loop
default :
log . Error ( fmt . Sprintf ( "unexpected control request #%d" , c ) )
}
}
2016-08-26 06:59:27 +00:00
}
2016-09-16 06:36:58 +00:00
changes <- svc . Status { State : svc . StopPending }
return
2016-08-26 06:59:27 +00:00
}