Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: svc.Run() as main routine blocker #1372

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"fmt"
"os"
"sort"

"github.com/go-kit/log/level"
"github.com/prometheus-community/windows_exporter/pkg/collector"
"github.com/prometheus-community/windows_exporter/pkg/exporter"
"github.com/prometheus-community/windows_exporter/pkg/windows_service"
"golang.org/x/sys/windows/svc"
)

func main() {
exporter := exporter.New()
if exporter.PrintCollectors() {
collectorNames := collector.Available()
sort.Strings(collectorNames)
fmt.Printf("Available collectors:\n")
for _, n := range collectorNames {
fmt.Printf(" - %s\n", n)
}
os.Exit(0)
}
isWinService, err := svc.IsWindowsService()
if err != nil {
_ = level.Error(exporter.GetLogger()).Log("Failed to detect [IsWindowsService]: ", "err", err)
os.Exit(1)
}
if isWinService {
windows_service.Run(exporter)
} else {
exporter.RunAsCli()
}
}
218 changes: 119 additions & 99 deletions exporter.go → pkg/exporter/exporter.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
//go:build windows

package main
package exporter

import (
"encoding/json"
"fmt"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"os/user"
"runtime"
"sort"
"strings"
"syscall"

// 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"
winlog "github.com/prometheus-community/windows_exporter/pkg/log"
"github.com/prometheus-community/windows_exporter/pkg/types"
"github.com/prometheus-community/windows_exporter/pkg/utils"
"github.com/prometheus-community/windows_exporter/pkg/wmi"

"github.com/alecthomas/kingpin/v2"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus-community/windows_exporter/pkg/collector"
"github.com/prometheus-community/windows_exporter/pkg/config"
Expand All @@ -30,6 +30,43 @@ import (
webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
)

var (
app = kingpin.New("windows_exporter", "A metrics collector for Windows.")
configFile = app.Flag(
"config.file",
"YAML configuration file to use. Values set in this file will be overridden by CLI flags.",
).String()
insecure_skip_verify = app.Flag(
"config.file.insecure-skip-verify",
"Skip TLS verification in loading YAML configuration.",
).Default("false").Bool()
metricsPath = app.Flag(
"telemetry.path",
"URL path for surfacing collected metrics.",
).Default("/metrics").String()
disableExporterMetrics = app.Flag(
"web.disable-exporter-metrics",
"Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).",
).Bool()
maxRequests = app.Flag(
"telemetry.max-requests",
"Maximum number of concurrent requests. 0 to disable.",
).Default("5").Int()
enabledCollectors = app.Flag(
"collectors.enabled",
"Comma-separated list of collectors to use. Use '[defaults]' as a placeholder for all the collectors enabled by default.").
Default(types.DefaultCollectors).String()
printCollectors = app.Flag(
"collectors.print",
"If true, print available collectors and exit.",
).Bool()
timeoutMargin = app.Flag(
"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()
webConfig = webflag.AddFlags(app, ":9182")
)

// Same struct prometheus uses for their /version endpoint.
// Separate copy to avoid pulling all of prometheus as a dependency
type prometheusVersion struct {
Expand All @@ -41,73 +78,75 @@ type prometheusVersion struct {
GoVersion string `json:"goVersion"`
}

func main() {
app := kingpin.New("windows_exporter", "A metrics collector for Windows.")
var (
configFile = app.Flag(
"config.file",
"YAML configuration file to use. Values set in this file will be overridden by CLI flags.",
).String()
insecure_skip_verify = app.Flag(
"config.file.insecure-skip-verify",
"Skip TLS verification in loading YAML configuration.",
).Default("false").Bool()
webConfig = webflag.AddFlags(app, ":9182")
metricsPath = app.Flag(
"telemetry.path",
"URL path for surfacing collected metrics.",
).Default("/metrics").String()
disableExporterMetrics = app.Flag(
"web.disable-exporter-metrics",
"Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).",
).Bool()
maxRequests = app.Flag(
"telemetry.max-requests",
"Maximum number of concurrent requests. 0 to disable.",
).Default("5").Int()
enabledCollectors = app.Flag(
"collectors.enabled",
"Comma-separated list of collectors to use. Use '[defaults]' as a placeholder for all the collectors enabled by default.").
Default(types.DefaultCollectors).String()
printCollectors = app.Flag(
"collectors.print",
"If true, print available collectors and exit.",
).Bool()
timeoutMargin = app.Flag(
"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()
)

winlogConfig := &winlog.Config{}
flag.AddFlags(app, winlogConfig)
type Exporter struct {
logger log.Logger
winlogConfig winlog.Config
collectors collector.Collectors
}

func New() *Exporter {
e := &Exporter{}

e.winlogConfig = winlog.Config{}
flag.AddFlags(app, &e.winlogConfig)

app.Version(version.Print("windows_exporter"))
app.HelpFlag.Short('h')

// Initialize collectors before loading and parsing CLI arguments
collectors := collector.NewWithFlags(app)
e.collectors = collector.NewWithFlags(app)

e.loadConfiguration()
e.initiateWmi()
e.printRunAsUser()
return e
}

func (e *Exporter) GetLogger() log.Logger {
return e.logger
}

func (e *Exporter) PrintCollectors() bool {
return *printCollectors
}

func (e *Exporter) Start() {
e.buildCollectors()
e.buildAndStartHttpServer()
}

func (e *Exporter) RunAsCli() {
e.Start()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
<-sigs
_ = level.Info(e.logger).Log("msg", "Shutting down windows_exporter")
}

func (e *Exporter) loadConfiguration() {
// Load values from configuration file(s). Executable flags must first be parsed, in order
// to load the specified file(s).
kingpin.MustParse(app.Parse(os.Args[1:]))
logger, err := winlog.New(winlogConfig)

logger, err := winlog.New(&e.winlogConfig)
if err != nil {
_ = level.Error(logger).Log("err", err)
fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
os.Exit(1)
}
e.logger = log.With(logger, "main", "exporter")
e.collectors.SetLogger(logger)

_ = level.Debug(logger).Log("msg", "Logging has Started")
_ = level.Debug(e.logger).Log("msg", "Logging has Started")

if *configFile != "" {
resolver, err := config.NewResolver(*configFile, logger, *insecure_skip_verify)
if err != nil {
_ = level.Error(logger).Log("msg", "could not load config file", "err", err)
_ = level.Error(e.logger).Log("msg", "could not load config file", "err", err)
os.Exit(1)
}
err = resolver.Bind(app, os.Args[1:])
if err != nil {
_ = level.Error(logger).Log("err", err)
_ = level.Error(e.logger).Log("err", err)
os.Exit(1)
}

Expand All @@ -118,60 +157,48 @@ func main() {

// Parse flags once more to include those discovered in configuration file(s).
kingpin.MustParse(app.Parse(os.Args[1:]))
}
}

logger, err = winlog.New(winlogConfig)
if err != nil {
_ = level.Error(logger).Log("err", err)
os.Exit(1)
}
func (e *Exporter) initiateWmi() {
if err := wmi.InitWbem(e.logger); err != nil {
_ = level.Error(e.logger).Log("Initiate SWbemServices failed with: ", "err", err)
os.Exit(1)
}
}

if *printCollectors {
collectorNames := collector.Available()
sort.Strings(collectorNames)
func (e *Exporter) printRunAsUser() {
if u, err := user.Current(); err != nil {
_ = level.Warn(e.logger).Log("msg", "Unable to determine which user is running this exporter. More info: https://github.com/golang/go/issues/37348")
} else {
_ = level.Info(e.logger).Log("msg", fmt.Sprintf("Running as %v", u.Username))

fmt.Printf("Available collectors:\n")
for _, n := range collectorNames {
fmt.Printf(" - %s\n", n)
if strings.Contains(u.Username, "ContainerAdministrator") || strings.Contains(u.Username, "ContainerUser") {
_ = level.Warn(e.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.")
}

return
}

if err = wmi.InitWbem(logger); err != nil {
_ = level.Error(logger).Log("err", err)
os.Exit(1)
}
}

func (e *Exporter) buildCollectors() {
enabledCollectorList := utils.ExpandEnabledCollectors(*enabledCollectors)
collectors.Enable(enabledCollectorList)
collectors.SetLogger(logger)
e.collectors.Enable(enabledCollectorList)
_ = level.Info(e.logger).Log("msg", fmt.Sprintf("Enabled collectors: %v", strings.Join(enabledCollectorList, ", ")))

// Initialize collectors before loading
err = collectors.Build()
err := e.collectors.Build()
if err != nil {
_ = level.Error(logger).Log("msg", "Couldn't load collectors", "err", err)
_ = level.Error(e.logger).Log("msg", "Couldn't load collectors", "err", err)
os.Exit(1)
}
}

if u, err := user.Current(); err != nil {
_ = level.Warn(logger).Log("msg", "Unable to determine which user is running this exporter. More info: https://github.com/golang/go/issues/37348")
} else {
_ = level.Info(logger).Log("msg", fmt.Sprintf("Running as %v", u.Username))

if strings.Contains(u.Username, "ContainerAdministrator") || strings.Contains(u.Username, "ContainerUser") {
_ = 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.")
}
}

_ = level.Info(logger).Log("msg", fmt.Sprintf("Enabled collectors: %v", strings.Join(enabledCollectorList, ", ")))

http.HandleFunc(*metricsPath, withConcurrencyLimit(*maxRequests, collectors.BuildServeHTTP(*disableExporterMetrics, *timeoutMargin)))
func (e *Exporter) buildAndStartHttpServer() {
http.HandleFunc(*metricsPath, withConcurrencyLimit(*maxRequests, e.collectors.BuildServeHTTP(*disableExporterMetrics, *timeoutMargin)))
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 {
_ = level.Debug(logger).Log("Failed to write to stream", "err", err)
_ = level.Debug(e.logger).Log("Failed to write to stream", "err", err)
}
})
http.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -211,30 +238,23 @@ func main() {
}
landingPage, err := web.NewLandingPage(landingConfig)
if err != nil {
_ = level.Error(logger).Log("msg", "failed to generate landing page", "err", err)
_ = level.Error(e.logger).Log("msg", "failed to generate landing page", "err", err)
os.Exit(1)
}
http.Handle("/", landingPage)
}

_ = 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))
_ = level.Info(e.logger).Log("msg", "Starting windows_exporter", "version", version.Info())
_ = level.Info(e.logger).Log("msg", "Build context", "build_context", version.BuildContext())
_ = level.Debug(e.logger).Log("msg", "Go MAXPROCS", "procs", runtime.GOMAXPROCS(0))

go func() {
server := &http.Server{}
if err := web.ListenAndServe(server, webConfig, logger); err != nil {
_ = level.Error(logger).Log("msg", "cannot start windows_exporter", "err", err)
if err := web.ListenAndServe(server, webConfig, e.logger); err != nil {
_ = level.Error(e.logger).Log("msg", "cannot start windows_exporter", "err", err)
os.Exit(1)
}
}()

for {
if <-initiate.StopCh {
_ = level.Info(logger).Log("msg", "Shutting down windows_exporter")
break
}
}
}

func withConcurrencyLimit(n int, next http.HandlerFunc) http.HandlerFunc {
Expand Down
Loading