2021-11-05 17:12:02 +00:00
//go:build windows
2018-11-30 00:51:12 +00:00
// +build windows
2016-08-26 06:59:27 +00:00
package main
import (
2022-08-24 09:04:23 +00:00
//Its important that we do these first so that we can register with the windows service control ASAP to avoid timeouts
2022-08-23 13:57:16 +00:00
"github.com/prometheus-community/windows_exporter/initiate"
2023-04-22 10:17:51 +00:00
winlog "github.com/prometheus-community/windows_exporter/log"
2022-08-23 13:57:16 +00:00
2020-06-23 10:48:19 +00:00
"encoding/json"
2016-09-01 14:04:43 +00:00
"fmt"
2023-04-22 10:17:51 +00:00
stdlog "log"
2016-08-26 06:59:27 +00:00
"net/http"
2019-08-08 19:09:21 +00:00
_ "net/http/pprof"
2020-10-22 01:19:22 +00:00
"os"
2020-07-30 23:36:58 +00:00
"os/user"
2023-04-22 10:17:51 +00:00
"runtime"
2016-09-01 14:04:43 +00:00
"sort"
2019-06-23 20:01:43 +00:00
"strconv"
2016-09-01 14:04:43 +00:00
"strings"
"time"
2016-08-26 06:59:27 +00:00
2020-05-24 18:38:05 +00:00
"github.com/prometheus-community/windows_exporter/collector"
2020-10-22 04:07:30 +00:00
"github.com/prometheus-community/windows_exporter/config"
2023-04-22 10:17:51 +00:00
"github.com/prometheus-community/windows_exporter/log/flag"
2023-03-12 00:27:31 +00:00
"github.com/yusufpapurcu/wmi"
2022-08-23 13:57:16 +00:00
2023-03-12 23:32:17 +00:00
"github.com/alecthomas/kingpin/v2"
2023-04-22 10:17:51 +00:00
"github.com/go-kit/log"
"github.com/go-kit/log/level"
2016-08-26 06:59:27 +00:00
"github.com/prometheus/client_golang/prometheus"
2022-04-29 01:18:05 +00:00
"github.com/prometheus/client_golang/prometheus/collectors"
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/version"
2021-02-23 23:23:38 +00:00
"github.com/prometheus/exporter-toolkit/web"
webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
2016-08-26 06:59:27 +00:00
)
2020-06-23 10:48:19 +00:00
// Same struct prometheus uses for their /version endpoint.
// Separate copy to avoid pulling all of prometheus as a dependency
type prometheusVersion struct {
Version string ` json:"version" `
Revision string ` json:"revision" `
Branch string ` json:"branch" `
BuildUser string ` json:"buildUser" `
BuildDate string ` json:"buildDate" `
GoVersion string ` json:"goVersion" `
}
2016-09-01 14:04:43 +00:00
const (
2018-04-03 19:37:10 +00:00
defaultCollectors = "cpu,cs,logical_disk,net,os,service,system,textfile"
2017-03-04 11:44:47 +00:00
defaultCollectorsPlaceholder = "[defaults]"
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 ) )
2018-04-05 05:11:36 +00:00
for s := range unique {
2017-03-04 11:44:47 +00:00
result = append ( result , s )
}
return result
}
2023-04-22 10:17:51 +00:00
func loadCollectors ( list string , logger log . Logger ) ( map [ string ] collector . Collector , error ) {
2016-09-01 14:04:43 +00:00
collectors := map [ string ] collector . Collector { }
2017-03-04 11:44:47 +00:00
enabled := expandEnabledCollectors ( list )
for _ , name := range enabled {
2023-04-22 10:17:51 +00:00
c , err := collector . Build ( name , logger )
2016-09-01 14:04:43 +00:00
if err != nil {
return nil , err
}
collectors [ name ] = c
}
2020-02-09 20:09:26 +00:00
2016-09-01 14:04:43 +00:00
return collectors , nil
}
2023-04-22 10:17:51 +00:00
func initWbem ( logger log . Logger ) {
2017-06-26 19:03:17 +00:00
// This initialization prevents a memory leak on WMF 5+. See
2020-05-24 18:45:54 +00:00
// https://github.com/prometheus-community/windows_exporter/issues/77 and
// linked issues for details.
2023-06-08 00:29:50 +00:00
_ = level . Debug ( logger ) . Log ( "msg" , "Initializing SWbemServices" )
2017-06-26 19:03:17 +00:00
s , err := wmi . InitializeSWbemServices ( wmi . DefaultClient )
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Error ( logger ) . Log ( "err" , err )
2023-04-22 10:17:51 +00:00
os . Exit ( 1 )
2017-06-26 19:03:17 +00:00
}
2018-06-06 08:31:50 +00:00
wmi . DefaultClient . AllowMissingFields = true
2017-06-26 19:03:17 +00:00
wmi . DefaultClient . SWbemServicesClient = s
}
2016-08-26 06:59:27 +00:00
func main ( ) {
2023-04-16 09:45:07 +00:00
app := kingpin . New ( "windows_exporter" , "A metrics collector for Windows." )
2016-08-26 06:59:27 +00:00
var (
2023-04-16 09:45:07 +00:00
configFile = app . Flag (
2020-10-22 04:07:30 +00:00
"config.file" ,
2021-12-18 18:18:16 +00:00
"YAML configuration file to use. Values set in this file will be overridden by CLI flags." ,
2020-11-03 07:07:23 +00:00
) . String ( )
2023-04-16 09:45:07 +00:00
webConfig = webflag . AddFlags ( app , ":9182" )
metricsPath = app . Flag (
2017-08-12 06:44:59 +00:00
"telemetry.path" ,
"URL path for surfacing collected metrics." ,
) . Default ( "/metrics" ) . String ( )
2023-04-16 09:45:07 +00:00
disableExporterMetrics = app . Flag (
2023-03-20 20:26:38 +00:00
"web.disable-exporter-metrics" ,
"Exclude metrics about the exporter itself (promhttp_*, process_*, go_*)." ,
) . Bool ( )
2023-04-16 09:45:07 +00:00
maxRequests = app . Flag (
2020-03-02 21:39:39 +00:00
"telemetry.max-requests" ,
"Maximum number of concurrent requests. 0 to disable." ,
) . Default ( "5" ) . Int ( )
2023-04-16 09:45:07 +00:00
enabledCollectors = app . Flag (
2017-08-12 06:44:59 +00:00
"collectors.enabled" ,
2018-08-06 01:39:43 +00:00
"Comma-separated list of collectors to use. Use '[defaults]' as a placeholder for all the collectors enabled by default." ) .
2020-02-09 20:09:26 +00:00
Default ( defaultCollectors ) . String ( )
2023-04-16 09:45:07 +00:00
printCollectors = app . Flag (
2017-08-12 06:44:59 +00:00
"collectors.print" ,
"If true, print available collectors and exit." ,
) . Bool ( )
2023-04-16 09:45:07 +00:00
timeoutMargin = app . Flag (
2019-06-23 20:01:43 +00:00
"scrape.timeout-margin" ,
"Seconds to subtract from the timeout allowed by the client. Tune to allow for overhead or high loads." ,
) . Default ( "0.5" ) . Float64 ( )
2016-08-26 06:59:27 +00:00
)
2023-04-22 10:17:51 +00:00
winlogConfig := & winlog . Config { }
flag . AddFlags ( app , winlogConfig )
2023-04-16 09:45:07 +00:00
app . Version ( version . Print ( "windows_exporter" ) )
app . HelpFlag . Short ( 'h' )
// Initialize collectors before loading and parsing CLI arguments
collector . RegisterCollectorsFlags ( app )
2020-10-22 04:07:30 +00:00
// Load values from configuration file(s). Executable flags must first be parsed, in order
// to load the specified file(s).
2023-04-16 09:45:07 +00:00
kingpin . MustParse ( app . Parse ( os . Args [ 1 : ] ) )
2023-04-22 10:17:51 +00:00
logger , err := winlog . New ( winlogConfig )
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Error ( logger ) . Log ( "err" , err )
2023-04-22 10:17:51 +00:00
os . Exit ( 1 )
}
2023-06-08 00:29:50 +00:00
_ = level . Debug ( logger ) . Log ( "msg" , "Logging has Started" )
2020-11-03 07:07:23 +00:00
if * configFile != "" {
2023-04-22 10:17:51 +00:00
resolver , err := config . NewResolver ( * configFile , logger )
2020-11-03 07:07:23 +00:00
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Error ( logger ) . Log ( "msg" , "could not load config file" , "err" , err )
2023-04-22 10:17:51 +00:00
os . Exit ( 1 )
2020-11-03 07:07:23 +00:00
}
2023-04-16 09:45:07 +00:00
err = resolver . Bind ( app , os . Args [ 1 : ] )
2020-11-03 07:07:23 +00:00
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Error ( logger ) . Log ( "err" , err )
2023-04-22 10:17:51 +00:00
os . Exit ( 1 )
2020-11-03 07:07:23 +00:00
}
2022-12-03 23:44:53 +00:00
// NOTE: This is temporary fix for issue #1092, calling kingpin.Parse
// twice makes slices flags duplicate its value, this clean up
// the first parse before the second call.
* webConfig . WebListenAddresses = ( * webConfig . WebListenAddresses ) [ 1 : ]
2020-11-03 07:07:23 +00:00
// Parse flags once more to include those discovered in configuration file(s).
2023-04-16 09:45:07 +00:00
kingpin . MustParse ( app . Parse ( os . Args [ 1 : ] ) )
2023-04-22 10:17:51 +00:00
logger , err = winlog . New ( winlogConfig )
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Error ( logger ) . Log ( "err" , err )
2023-04-22 10:17:51 +00:00
os . Exit ( 1 )
}
2020-10-22 04:07:30 +00:00
}
2016-09-01 14:04:43 +00:00
if * printCollectors {
2020-02-09 20:09:26 +00:00
collectors := collector . Available ( )
collectorNames := make ( sort . StringSlice , 0 , len ( collectors ) )
for _ , n := range collectors {
2016-09-01 14:04:43 +00:00
collectorNames = append ( collectorNames , n )
}
collectorNames . Sort ( )
fmt . Printf ( "Available collectors:\n" )
for _ , n := range collectorNames {
fmt . Printf ( " - %s\n" , n )
}
return
}
2023-04-22 10:17:51 +00:00
initWbem ( logger )
2017-06-26 19:03:17 +00:00
2022-12-21 05:37:14 +00:00
// Initialize collectors before loading
2023-04-22 10:17:51 +00:00
collector . RegisterCollectors ( logger )
2022-12-21 05:37:14 +00:00
2023-04-22 10:17:51 +00:00
collectors , err := loadCollectors ( * enabledCollectors , logger )
2016-09-01 14:04:43 +00:00
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Error ( logger ) . Log ( "msg" , "Couldn't load collectors" , "err" , err )
2023-04-22 10:17:51 +00:00
os . Exit ( 1 )
2016-09-01 14:04:43 +00:00
}
2020-07-30 23:36:58 +00:00
u , err := user . Current ( )
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Error ( logger ) . Log ( "err" , err )
2023-04-22 10:17:51 +00:00
os . Exit ( 1 )
2020-07-30 23:36:58 +00:00
}
2023-06-08 00:29:50 +00:00
_ = level . Info ( logger ) . Log ( "msg" , fmt . Sprintf ( "Running as %v" , u . Username ) )
2020-07-30 23:36:58 +00:00
if strings . Contains ( u . Username , "ContainerAdministrator" ) || strings . Contains ( u . Username , "ContainerUser" ) {
2023-06-08 00:29:50 +00:00
_ = level . Warn ( logger ) . Log ( "msg" , "Running as a preconfigured Windows Container user. This may mean you do not have Windows HostProcess containers configured correctly and some functionality will not work as expected." )
2020-07-30 23:36:58 +00:00
}
2023-06-08 00:29:50 +00:00
_ = level . Info ( logger ) . Log ( "msg" , fmt . Sprintf ( "Enabled collectors: %v" , strings . Join ( keys ( collectors ) , ", " ) ) )
2016-09-01 14:04:43 +00:00
2019-06-23 20:01:43 +00:00
h := & metricsHandler {
2023-03-20 20:26:38 +00:00
timeoutMargin : * timeoutMargin ,
includeExporterMetrics : * disableExporterMetrics ,
2023-04-17 09:37:13 +00:00
collectorFactory : func ( timeout time . Duration , requestedCollectors [ ] string ) ( error , * collector . Prometheus ) {
2020-10-26 13:01:25 +00:00
filteredCollectors := make ( map [ string ] collector . Collector )
// scrape all enabled collectors if no collector is requested
if len ( requestedCollectors ) == 0 {
filteredCollectors = collectors
}
for _ , name := range requestedCollectors {
col , exists := collectors [ name ]
if ! exists {
return fmt . Errorf ( "unavailable collector: %s" , name ) , nil
}
filteredCollectors [ name ] = col
}
2023-04-22 10:17:51 +00:00
return nil , collector . NewPrometheus ( timeout , filteredCollectors , logger )
2019-06-23 20:01:43 +00:00
} ,
2023-04-22 10:17:51 +00:00
logger : logger ,
2019-05-15 19:22:29 +00:00
}
2016-08-26 06:59:27 +00:00
2020-03-02 21:39:39 +00:00
http . HandleFunc ( * metricsPath , withConcurrencyLimit ( * maxRequests , h . ServeHTTP ) )
2023-04-22 10:17:51 +00:00
http . HandleFunc ( "/health" , func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err := fmt . Fprintln ( w , ` { "status":"ok"} ` )
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Debug ( logger ) . Log ( "Failed to write to stream" , "err" , err )
2023-04-22 10:17:51 +00:00
}
} )
2020-06-23 10:48:19 +00:00
http . HandleFunc ( "/version" , func ( w http . ResponseWriter , r * http . Request ) {
// we can't use "version" directly as it is a package, and not an object that
// can be serialized.
err := json . NewEncoder ( w ) . Encode ( prometheusVersion {
Version : version . Version ,
Revision : version . Revision ,
Branch : version . Branch ,
BuildUser : version . BuildUser ,
BuildDate : version . BuildDate ,
GoVersion : version . GoVersion ,
} )
if err != nil {
http . Error ( w , fmt . Sprintf ( "error encoding JSON: %s" , err ) , http . StatusInternalServerError )
}
} )
2023-03-25 09:32:18 +00:00
if * metricsPath != "/" && * metricsPath != "" {
landingConfig := web . LandingConfig {
Name : "Windows Exporter" ,
Description : "Prometheus Exporter for Windows servers" ,
Version : version . Info ( ) ,
Links : [ ] web . LandingLinks {
{
Address : * metricsPath ,
Text : "Metrics" ,
} ,
{
Address : "/health" ,
Text : "Health Check" ,
} ,
{
Address : "/version" ,
Text : "Version Info" ,
} ,
} ,
}
landingPage , err := web . NewLandingPage ( landingConfig )
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Error ( logger ) . Log ( "msg" , "failed to generate landing page" , "err" , err )
2023-04-22 10:17:51 +00:00
os . Exit ( 1 )
2023-03-25 09:32:18 +00:00
}
http . Handle ( "/" , landingPage )
}
2016-08-26 06:59:27 +00:00
2023-06-08 00:29:50 +00:00
_ = level . Info ( logger ) . Log ( "msg" , "Starting windows_exporter" , "version" , version . Info ( ) )
_ = level . Info ( logger ) . Log ( "msg" , "Build context" , "build_context" , version . BuildContext ( ) )
_ = level . Debug ( logger ) . Log ( "msg" , "Go MAXPROCS" , "procs" , runtime . GOMAXPROCS ( 0 ) )
2016-09-01 14:04:43 +00:00
2016-09-16 06:36:58 +00:00
go func ( ) {
2022-10-24 11:12:46 +00:00
server := & http . Server { }
2023-04-22 10:17:51 +00:00
if err := web . ListenAndServe ( server , webConfig , logger ) ; err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Error ( logger ) . Log ( "msg" , "cannot start windows_exporter" , "err" , err )
2023-04-22 10:17:51 +00:00
os . Exit ( 1 )
2021-01-03 14:54:32 +00:00
}
2016-09-16 06:36:58 +00:00
} ( )
for {
2022-08-23 13:57:16 +00:00
if <- initiate . StopCh {
2023-06-08 00:29:50 +00:00
_ = level . Info ( logger ) . Log ( "msg" , "Shutting down windows_exporter" )
2016-09-16 06:36:58 +00:00
break
}
}
}
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
}
2020-03-02 21:39:39 +00:00
func withConcurrencyLimit ( n int , next http . HandlerFunc ) http . HandlerFunc {
if n <= 0 {
return next
}
sem := make ( chan struct { } , n )
return func ( w http . ResponseWriter , r * http . Request ) {
select {
case sem <- struct { } { } :
defer func ( ) { <- sem } ( )
default :
w . WriteHeader ( http . StatusServiceUnavailable )
_ , _ = w . Write ( [ ] byte ( "Too many concurrent requests" ) )
return
}
next ( w , r )
}
}
2019-06-23 20:01:43 +00:00
type metricsHandler struct {
2023-03-20 20:26:38 +00:00
timeoutMargin float64
includeExporterMetrics bool
2023-04-17 09:37:13 +00:00
collectorFactory func ( timeout time . Duration , requestedCollectors [ ] string ) ( error , * collector . Prometheus )
2023-04-22 10:17:51 +00:00
logger log . Logger
2019-06-23 20:01:43 +00:00
}
func ( mh * metricsHandler ) ServeHTTP ( w http . ResponseWriter , r * http . Request ) {
const defaultTimeout = 10.0
var timeoutSeconds float64
if v := r . Header . Get ( "X-Prometheus-Scrape-Timeout-Seconds" ) ; v != "" {
var err error
timeoutSeconds , err = strconv . ParseFloat ( v , 64 )
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Warn ( mh . logger ) . Log ( "msg" , fmt . Sprintf ( "Couldn't parse X-Prometheus-Scrape-Timeout-Seconds: %q. Defaulting timeout to %f" , v , defaultTimeout ) )
2019-06-23 20:01:43 +00:00
}
}
if timeoutSeconds == 0 {
timeoutSeconds = defaultTimeout
}
timeoutSeconds = timeoutSeconds - mh . timeoutMargin
reg := prometheus . NewRegistry ( )
2020-10-26 13:01:25 +00:00
err , wc := mh . collectorFactory ( time . Duration ( timeoutSeconds * float64 ( time . Second ) ) , r . URL . Query ( ) [ "collect[]" ] )
if err != nil {
2023-06-08 00:29:50 +00:00
_ = level . Warn ( mh . logger ) . Log ( "msg" , "Couldn't create filtered metrics handler" , "err" , err )
2020-10-26 13:01:25 +00:00
w . WriteHeader ( http . StatusBadRequest )
2021-12-24 10:19:05 +00:00
w . Write ( [ ] byte ( fmt . Sprintf ( "Couldn't create filtered metrics handler: %s" , err ) ) ) //nolint:errcheck
2020-10-26 13:01:25 +00:00
return
}
reg . MustRegister ( wc )
2023-03-20 20:26:38 +00:00
if ! mh . includeExporterMetrics {
reg . MustRegister (
collectors . NewProcessCollector ( collectors . ProcessCollectorOpts { } ) ,
collectors . NewGoCollector ( ) ,
version . NewCollector ( "windows_exporter" ) ,
)
}
2019-06-23 20:01:43 +00:00
2023-04-22 10:17:51 +00:00
h := promhttp . HandlerFor ( reg , promhttp . HandlerOpts {
ErrorLog : stdlog . New ( log . NewStdlibAdapter ( level . Error ( mh . logger ) ) , "" , stdlog . Lshortfile ) ,
} )
2019-06-23 20:01:43 +00:00
h . ServeHTTP ( w , r )
}