2021-11-05 17:12:02 +00:00
//go:build windows
2018-11-30 00:51:12 +00:00
2024-05-03 05:30:31 +00:00
//go:generate go run github.com/tc-hib/go-winres@v0.3.3 make --product-version=git-tag --file-version=git-tag --arch=amd64,arm64
2016-08-26 06:59:27 +00:00
package main
2024-08-10 20:05:33 +00:00
//goland:noinspection GoUnsortedImport
//nolint:gofumpt
2016-08-26 06:59:27 +00:00
import (
2024-08-10 20:05:33 +00:00
// Its important that we do these first so that we can register with the Windows service control ASAP to avoid timeouts.
"github.com/prometheus-community/windows_exporter/pkg/initiate"
2024-08-05 13:50:41 +00:00
"context"
2024-08-24 17:14:38 +00:00
"errors"
2016-09-01 14:04:43 +00:00
"fmt"
2024-09-10 22:34:10 +00:00
"log/slog"
2016-08-26 06:59:27 +00:00
"net/http"
2024-05-03 05:30:40 +00:00
"net/http/pprof"
2020-10-22 01:19:22 +00:00
"os"
2024-08-05 13:50:41 +00:00
"os/signal"
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"
"strings"
2024-08-05 13:50:41 +00:00
"time"
2022-08-23 13:57:16 +00:00
2023-03-12 23:32:17 +00:00
"github.com/alecthomas/kingpin/v2"
2023-11-04 19:51:35 +00:00
"github.com/prometheus-community/windows_exporter/pkg/collector"
"github.com/prometheus-community/windows_exporter/pkg/config"
2024-09-10 22:34:10 +00:00
"github.com/prometheus-community/windows_exporter/pkg/httphandler"
2024-08-05 13:50:41 +00:00
winlog "github.com/prometheus-community/windows_exporter/pkg/log"
2023-11-04 19:51:35 +00:00
"github.com/prometheus-community/windows_exporter/pkg/log/flag"
2024-08-05 13:50:41 +00:00
"github.com/prometheus-community/windows_exporter/pkg/types"
"github.com/prometheus-community/windows_exporter/pkg/utils"
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"
2024-05-17 20:51:33 +00:00
"golang.org/x/sys/windows"
2016-08-26 06:59:27 +00:00
)
2024-09-10 22:34:10 +00:00
func main ( ) {
2024-09-28 17:53:12 +00:00
exitCode := run ( )
// If we are running as a service, we need to signal the service control manager that we are done.
if ! initiate . IsService {
os . Exit ( exitCode )
}
initiate . ExitCodeCh <- exitCode
// Wait for the service control manager to signal that we are done.
<- initiate . StopCh
2024-05-17 20:51:33 +00:00
}
2024-09-10 22:34:10 +00:00
func run ( ) int {
2023-04-16 09:45:07 +00:00
app := kingpin . New ( "windows_exporter" , "A metrics collector for Windows." )
2024-09-10 22:34:10 +00:00
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 ( )
2024-08-29 22:26:15 +00:00
insecureSkipVerify = app . Flag (
2023-07-11 05:36:36 +00:00
"config.file.insecure-skip-verify" ,
"Skip TLS verification in loading YAML configuration." ,
) . Default ( "false" ) . Bool ( )
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." ) .
2023-11-04 19:51:35 +00:00
Default ( types . 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 ( )
2024-05-03 05:30:40 +00:00
debugEnabled = app . Flag (
"debug.enabled" ,
"If true, windows_exporter will expose debug endpoints under /debug/pprof." ,
) . Default ( "false" ) . Bool ( )
2024-05-17 20:51:33 +00:00
processPriority = app . Flag (
"process.priority" ,
"Priority of the exporter process. Higher priorities may improve exporter responsiveness during periods of system load. Can be one of [\"realtime\", \"high\", \"abovenormal\", \"normal\", \"belownormal\", \"low\"]" ,
) . Default ( "normal" ) . String ( )
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
2023-11-04 19:51:35 +00:00
collectors := collector . NewWithFlags ( 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).
2024-09-10 22:34:10 +00:00
if _ , err := app . Parse ( os . Args [ 1 : ] ) ; err != nil {
//nolint:sloglint // we do not have an logger yet
slog . Error ( "Failed to parse CLI args" ,
slog . Any ( "err" , err ) ,
)
return 1
}
2023-04-22 10:17:51 +00:00
logger , err := winlog . New ( winlogConfig )
if err != nil {
2024-09-10 22:34:10 +00:00
//nolint:sloglint // we do not have an logger yet
slog . Error ( "failed to create logger" ,
slog . Any ( "err" , err ) ,
)
2023-04-22 10:17:51 +00:00
2024-09-10 22:34:10 +00:00
return 1
}
2023-11-04 19:51:35 +00:00
2020-11-03 07:07:23 +00:00
if * configFile != "" {
2024-08-29 22:26:15 +00:00
resolver , err := config . NewResolver ( * configFile , logger , * insecureSkipVerify )
2020-11-03 07:07:23 +00:00
if err != nil {
2024-09-10 22:34:10 +00:00
logger . Error ( "could not load config file" ,
slog . Any ( "err" , err ) ,
)
return 1
2020-11-03 07:07:23 +00:00
}
2024-09-10 22:34:10 +00:00
if err = resolver . Bind ( app , os . Args [ 1 : ] ) ; err != nil {
logger . Error ( "Failed to bind configuration" ,
slog . Any ( "err" , err ) ,
)
return 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).
2024-09-10 22:34:10 +00:00
if _ , err = app . Parse ( os . Args [ 1 : ] ) ; err != nil {
logger . Error ( "Failed to parse CLI args from YAML file" ,
slog . Any ( "err" , err ) ,
)
return 1
}
2023-04-22 10:17:51 +00:00
logger , err = winlog . New ( winlogConfig )
if err != nil {
2024-09-10 22:34:10 +00:00
//nolint:sloglint // we do not have an logger yet
slog . Error ( "failed to create logger" ,
slog . Any ( "err" , err ) ,
)
return 1
2023-04-22 10:17:51 +00:00
}
2020-10-22 04:07:30 +00:00
}
2016-09-01 14:04:43 +00:00
2024-09-10 22:34:10 +00:00
logger . Debug ( "Logging has Started" )
2023-11-04 19:51:35 +00:00
2024-09-10 22:34:10 +00:00
if * printCollectors {
printCollectorsToStdout ( )
2023-11-04 19:51:35 +00:00
2024-09-10 22:34:10 +00:00
return 0
2016-09-01 14:04:43 +00:00
}
2024-09-28 11:23:08 +00:00
if err = setPriorityWindows ( logger , os . Getpid ( ) , * processPriority ) ; err != nil {
logger . Error ( "failed to set process priority" ,
slog . Any ( "err" , err ) ,
)
2024-09-10 22:34:10 +00:00
2024-09-28 11:23:08 +00:00
return 1
2024-05-17 20:51:33 +00:00
}
2023-11-04 19:51:35 +00:00
enabledCollectorList := utils . ExpandEnabledCollectors ( * enabledCollectors )
collectors . Enable ( enabledCollectorList )
2017-06-26 19:03:17 +00:00
2022-12-21 05:37:14 +00:00
// Initialize collectors before loading
2024-09-10 22:34:10 +00:00
if err = collectors . Build ( logger ) ; err != nil {
logger . Error ( "Couldn't load collectors" ,
slog . Any ( "err" , err ) ,
)
return 1
2024-01-08 14:57:25 +00:00
}
2016-09-01 14:04:43 +00:00
2024-09-10 22:34:10 +00:00
if err = collectors . SetPerfCounterQuery ( logger ) ; err != nil {
logger . Error ( "Couldn't set performance counter query" ,
slog . Any ( "err" , err ) ,
)
2023-11-04 19:51:35 +00:00
2024-09-10 22:34:10 +00:00
return 1
2020-07-30 23:36:58 +00:00
}
2024-09-10 22:34:10 +00:00
logCurrentUser ( logger )
logger . Info ( "Enabled collectors: " + strings . Join ( enabledCollectorList , ", " ) )
2016-08-26 06:59:27 +00:00
2024-09-13 21:10:57 +00:00
if utils . PDHEnabled ( ) {
logger . Info ( "Using performance data helper from PHD.dll for performance counter collection. This is in experimental state." )
}
2024-05-03 05:30:40 +00:00
mux := http . NewServeMux ( )
2024-09-10 22:34:10 +00:00
mux . Handle ( "GET /health" , httphandler . NewHealthHandler ( ) )
mux . Handle ( "GET /version" , httphandler . NewVersionHandler ( ) )
mux . Handle ( "GET " + * metricsPath , httphandler . New ( logger , collectors , & httphandler . Options {
DisableExporterMetrics : * disableExporterMetrics ,
TimeoutMargin : * timeoutMargin ,
MaxRequests : * maxRequests ,
} ) )
2024-05-03 05:30:40 +00:00
if * debugEnabled {
2024-09-10 22:34:10 +00:00
mux . HandleFunc ( "GET /debug/pprof/" , pprof . Index )
mux . HandleFunc ( "GET /debug/pprof/cmdline" , pprof . Cmdline )
mux . HandleFunc ( "GET /debug/pprof/profile" , pprof . Profile )
mux . HandleFunc ( "GET /debug/pprof/symbol" , pprof . Symbol )
mux . HandleFunc ( "GET /debug/pprof/trace" , pprof . Trace )
2024-05-03 05:30:40 +00:00
}
2024-09-10 22:34:10 +00:00
logger . Info ( "Starting windows_exporter" ,
slog . String ( "version" , version . Version ) ,
slog . String ( "branch" , version . Branch ) ,
slog . String ( "revision" , version . GetRevision ( ) ) ,
slog . String ( "goversion" , version . GoVersion ) ,
slog . String ( "builddate" , version . BuildDate ) ,
slog . Int ( "maxprocs" , runtime . GOMAXPROCS ( 0 ) ) ,
)
2016-09-01 14:04:43 +00:00
2024-08-05 13:50:41 +00:00
server := & http . Server {
ReadHeaderTimeout : 5 * time . Second ,
IdleTimeout : 60 * time . Second ,
ReadTimeout : 5 * time . Second ,
2024-09-10 22:34:10 +00:00
WriteTimeout : 5 * time . Minute ,
2024-08-05 13:50:41 +00:00
Handler : mux ,
}
2024-09-10 22:34:10 +00:00
errCh := make ( chan error , 1 )
2016-09-16 06:36:58 +00:00
go func ( ) {
2024-08-24 17:14:38 +00:00
if err := web . ListenAndServe ( server , webConfig , logger ) ; err != nil && ! errors . Is ( err , http . ErrServerClosed ) {
2024-09-10 22:34:10 +00:00
errCh <- err
2021-01-03 14:54:32 +00:00
}
2024-09-10 22:34:10 +00:00
errCh <- nil
2016-09-16 06:36:58 +00:00
} ( )
2024-08-05 13:50:41 +00:00
ctx , stop := signal . NotifyContext ( context . Background ( ) , os . Interrupt , os . Kill )
defer stop ( )
select {
case <- ctx . Done ( ) :
2024-09-10 22:34:10 +00:00
logger . Info ( "Shutting down windows_exporter via kill signal" )
2024-08-05 13:50:41 +00:00
case <- initiate . StopCh :
2024-09-10 22:34:10 +00:00
logger . Info ( "Shutting down windows_exporter via service control" )
case err := <- errCh :
if err != nil {
logger . Error ( "Failed to start windows_exporter" ,
slog . Any ( "err" , err ) ,
)
return 1
}
2016-09-16 06:36:58 +00:00
}
2024-08-05 13:50:41 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Second )
defer cancel ( )
_ = server . Shutdown ( ctx )
2024-09-10 22:34:10 +00:00
logger . Info ( "windows_exporter has shut down" )
return 0
2016-09-16 06:36:58 +00:00
}
2024-09-10 22:34:10 +00:00
func printCollectorsToStdout ( ) {
collectorNames := collector . Available ( )
sort . Strings ( collectorNames )
fmt . Println ( "Available collectors:" ) //nolint:forbidigo
for _ , n := range collectorNames {
fmt . Printf ( " - %s\n" , n ) //nolint:forbidigo
2020-03-02 21:39:39 +00:00
}
2024-09-10 22:34:10 +00:00
}
func logCurrentUser ( logger * slog . Logger ) {
2024-09-28 11:23:08 +00:00
u , err := user . Current ( )
if err != nil {
logger . Warn ( "Unable to determine which user is running this exporter. More info: https://github.com/golang/go/issues/37348" ,
slog . Any ( "err" , err ) ,
)
2024-09-10 22:34:10 +00:00
return
2020-03-02 21:39:39 +00:00
}
2024-09-10 22:34:10 +00:00
2024-09-28 11:23:08 +00:00
logger . Info ( "Running as " + u . Username )
if strings . Contains ( u . Username , "ContainerAdministrator" ) || strings . Contains ( u . Username , "ContainerUser" ) {
logger . Warn ( "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." )
}
2024-09-10 22:34:10 +00:00
}
2024-09-28 11:23:08 +00:00
// setPriorityWindows sets the priority of the current process to the specified value.
func setPriorityWindows ( logger * slog . Logger , pid int , priority string ) error {
// Mapping of priority names to uin32 values required by windows.SetPriorityClass.
priorityStringToInt := map [ string ] uint32 {
"realtime" : windows . REALTIME_PRIORITY_CLASS ,
"high" : windows . HIGH_PRIORITY_CLASS ,
"abovenormal" : windows . ABOVE_NORMAL_PRIORITY_CLASS ,
"normal" : windows . NORMAL_PRIORITY_CLASS ,
"belownormal" : windows . BELOW_NORMAL_PRIORITY_CLASS ,
"low" : windows . IDLE_PRIORITY_CLASS ,
}
winPriority , ok := priorityStringToInt [ priority ]
// Only set process priority if a non-default and valid value has been set
if ! ok || winPriority != windows . NORMAL_PRIORITY_CLASS {
return nil
}
logger . Debug ( "setting process priority to " + priority )
2024-09-10 22:34:10 +00:00
// https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
handle , err := windows . OpenProcess (
windows . STANDARD_RIGHTS_REQUIRED | windows . SYNCHRONIZE | windows . SPECIFIC_RIGHTS_ALL ,
false , uint32 ( pid ) ,
)
if err != nil {
return fmt . Errorf ( "failed to open own process: %w" , err )
}
2024-09-28 11:23:08 +00:00
if err = windows . SetPriorityClass ( handle , winPriority ) ; err != nil {
2024-09-10 22:34:10 +00:00
return fmt . Errorf ( "failed to set priority class: %w" , err )
}
if err = windows . CloseHandle ( handle ) ; err != nil {
return fmt . Errorf ( "failed to close handle: %w" , err )
}
return nil
2020-03-02 21:39:39 +00:00
}