diff --git a/.golangci.yaml b/.golangci.yaml index 2d625578a..49662f63e 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -26,7 +26,10 @@ linters: - wrapcheck run: - timeout: 5m + timeout: 15m + +output: + sort-results: true linters-settings: gosec: diff --git a/.promu.yml b/.promu.yml index 911167003..9a736cb09 100644 --- a/.promu.yml +++ b/.promu.yml @@ -8,6 +8,7 @@ repository: build: binaries: - name: windows_exporter + path: ./cmd/windows_exporter tags: all: - trimpath diff --git a/cmd/windows_exporter/doc.go b/cmd/windows_exporter/doc.go new file mode 100644 index 000000000..244aa7fae --- /dev/null +++ b/cmd/windows_exporter/doc.go @@ -0,0 +1,195 @@ +// Copyright 2024 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. + +/* +The main package for the windows_exporter executable. + +usage: windows_exporter [] + +A metrics collector for Windows. + +Flags: + + -h, --[no-]help Show context-sensitive help (also try + --help-long and --help-man). + --config.file=CONFIG.FILE YAML configuration file to use. Values set in + this file will be overridden by CLI flags. + --[no-]config.file.insecure-skip-verify + Skip TLS verification in loading YAML + configuration. + --web.listen-address=:9182 ... + Addresses on which to expose metrics and web + interface. Repeatable for multiple addresses. + Examples: `:9100` or `[::1]:9100` for http, + `vsock://:9100` for vsock + --web.config.file="" Path to configuration file that can + enable TLS or authentication. See: + https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md + --telemetry.path="/metrics" + URL path for surfacing collected metrics. + --[no-]web.disable-exporter-metrics + Exclude metrics about the exporter itself + (promhttp_*, process_*, go_*). + --telemetry.max-requests=5 + Maximum number of concurrent requests. 0 to + disable. + --collectors.enabled="cpu,cs,memory,logical_disk,physical_disk,net,os,service,system" + Comma-separated list of collectors to use. + Use '[defaults]' as a placeholder for all the + collectors enabled by default. + --scrape.timeout-margin=0.5 + Seconds to subtract from the timeout allowed by + the client. Tune to allow for overhead or high + loads. + --[no-]debug.enabled If true, windows_exporter will expose debug + endpoints under /debug/pprof. + --process.priority="normal" + 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"] + --log.level=info Only log messages with the given severity or + above. One of: [debug, info, warn, error] + --log.format=logfmt Output format of log messages. One of: [logfmt, + json] + --log.file=stderr Output file of log messages. One of [stdout, + stderr, eventlog, ] + --[no-]version Show application version. + --collector.scheduled_task.exclude="" + Regexp of tasks to exclude. Task path must + both match include and not match exclude to be + included. + --collector.scheduled_task.include=".+" + Regexp of tasks to include. Task path must + both match include and not match exclude to be + included. + --[no-]collector.updates.online + Whether to search for updates online. + --collector.updates.scrape-interval=6h0m0s + Define the interval of scraping Windows Update + information. + --[no-]collector.exchange.list + List the collectors along with their perflib + object name/ids + --collector.exchange.enabled="ADAccessProcesses,TransportQueues,HttpProxy,ActiveSync,AvailabilityService,OutlookWebAccess,Autodiscover,WorkloadManagement,RpcClientAccess,MapiHttpEmsmdb" + Comma-separated list of collectors to use. + Defaults to all, if not specified. + --collector.net.nic-exclude="" + Regexp of NIC:s to exclude. NIC name must + both match include and not match exclude to be + included. + --collector.net.nic-include=".+" + Regexp of NIC:s to include. NIC name must + both match include and not match exclude to be + included. + --collector.net.enabled="metrics,nic_addresses" + Comma-separated list of collectors to use. + Defaults to all, if not specified. + --collector.mscluster.enabled="cluster,network,node,resource,resourcegroup" + Comma-separated list of collectors to use. + --collector.mssql.enabled="accessmethods,availreplica,bufman,databases,dbreplica,genstats,locks,memmgr,sqlerrors,sqlstats,transactions,waitstats" + Comma-separated list of collectors to use. + --collector.mssql.port=1433 + Port of MSSQL server used for + windows_mssql_info metric. + --collector.physical_disk.disk-exclude="" + Regexp of disks to exclude. Disk number must + both match include and not match exclude to be + included. + --collector.physical_disk.disk-include=".+" + Regexp of disks to include. Disk number must + both match include and not match exclude to be + included. + --collector.textfile.directories="C:\\Users\\Jan\\GolandProjects\\windows_exporter\\textfile_inputs" + Directory or Directories to read text files + with metrics from. + --collector.filetime.file-patterns="" + Comma-separated list of file patterns. + Each pattern is a glob pattern that can + contain `*`, `?`, and `**` (recursive). See + https://github.com/bmatcuk/doublestar#patterns + --collector.iis.app-exclude="" + Regexp of apps to exclude. App name must both + match include and not match exclude to be + included. + --collector.iis.app-include=".+" + Regexp of apps to include. App name must both + match include and not match exclude to be + included. + --collector.iis.site-exclude="" + Regexp of sites to exclude. Site name must + both match include and not match exclude to be + included. + --collector.iis.site-include=".+" + Regexp of sites to include. Site name must + both match include and not match exclude to be + included. + --collector.perfdata.objects="" + Objects of performance data to observe. + See docs for more information on how to use + this flag. By default, no objects are observed. + --collector.printer.include=".+" + Regular expression to match printers to collect + metrics for + --collector.printer.exclude="" + Regular expression to match printers to exclude + --collector.process.exclude="" + Regexp of processes to exclude. Process name + must both match include and not match exclude + to be included. + --collector.process.include=".+" + Regexp of processes to include. Process name + must both match include and not match exclude + to be included. + --[no-]collector.process.iis + Enable IIS worker process name queries. + May cause the collector to leak memory. + --collector.hyperv.enabled="datastore,dynamic_memory_balancer,dynamic_memory_vm,hypervisor_logical_processor,hypervisor_root_partition,hypervisor_root_virtual_processor,hypervisor_virtual_processor,legacy_network_adapter,virtual_machine_health_summary,virtual_machine_vid_partition,virtual_network_adapter,virtual_network_adapter_drop_reasons,virtual_smb,virtual_storage_device,virtual_switch" + Comma-separated list of collectors to use. + --collector.logical_disk.volume-exclude="" + Regexp of volumes to exclude. Volume name must + both match include and not match exclude to be + included. + --collector.logical_disk.volume-include=".+" + Regexp of volumes to include. Volume name must + both match include and not match exclude to be + included. + --collector.smtp.server-exclude="" + Regexp of virtual servers to exclude. Server + name must both match include and not match + exclude to be included. + --collector.smtp.server-include=".+" + Regexp of virtual servers to include. Server + name must both match include and not match + exclude to be included. + --collector.tcp.enabled="metrics,connections_state" + Comma-separated list of collectors to use. + Defaults to all, if not specified. + --collector.dfsr.sources-enabled="connection,folder,volume" + Comma-separated list of DFSR Perflib sources to + use. + --collector.service.exclude="" + Regexp of service to exclude. Service name (not + the display name!) must both match include and + not match exclude to be included. + --collector.service.include=".+" + Regexp of service to include. Process name (not + the display name!) must both match include and + not match exclude to be included. + --collector.time.enabled="system_time,ntp" + Comma-separated list of collectors to use. + Defaults to all, if not specified. ntp may not + available on all systems. +*/ +package main diff --git a/exporter.go b/cmd/windows_exporter/main.go similarity index 100% rename from exporter.go rename to cmd/windows_exporter/main.go diff --git a/internal/log/eventlog/eventlog.go b/internal/log/eventlog/eventlog.go index 47a50a07f..04abccaab 100644 --- a/internal/log/eventlog/eventlog.go +++ b/internal/log/eventlog/eventlog.go @@ -28,7 +28,7 @@ const ( // NeLogOemCode is a generic error log entry for OEMs to use to // elog errors from OEM value added services. // See: https://github.com/microsoft/win32metadata/blob/2f3c5282ce1024a712aeccd90d3aa50bf7a49e27/generation/WinSDK/RecompiledIdlHeaders/um/LMErrlog.h#L824-L845 - neLogOemCode = uint32(3299) + NeLogOemCode = uint32(3299) ) // Interface guard. @@ -62,5 +62,5 @@ func (w *Writer) Write(p []byte) (int, error) { ss := []*uint16{msg, nil, nil, nil, nil, nil, nil, nil, nil} - return len(p), windows.ReportEvent(w.handle, eType, 0, neLogOemCode, 0, 9, 0, &ss[0], nil) + return len(p), windows.ReportEvent(w.handle, eType, 0, NeLogOemCode, 0, 9, 0, &ss[0], nil) } diff --git a/internal/windowsservice/doc.go b/internal/windowsservice/doc.go new file mode 100644 index 000000000..69319d48d --- /dev/null +++ b/internal/windowsservice/doc.go @@ -0,0 +1,23 @@ +// Copyright 2024 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. + +//go:build windows + +// Package windowsservice allows initiating time-sensitive components like registering the Windows service +// as early as possible in the startup process. +// init functions are called in the order they are declared, so this package should be imported first. +// Declare imports on this package should be avoided where possible. +// +// Ref: https://github.com/prometheus-community/windows_exporter/issues/551#issuecomment-1220774835 + +package windowsservice diff --git a/internal/windowsservice/init.go b/internal/windowsservice/init.go new file mode 100644 index 000000000..273811197 --- /dev/null +++ b/internal/windowsservice/init.go @@ -0,0 +1,60 @@ +// Copyright 2024 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. + +package windowsservice + +import ( + "fmt" + "os" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc" +) + +var ( + // IsService is true if the exporter is running as a Windows service. + IsService bool + // ExitCodeCh is a channel to send the exit code return from the [github.com/prometheus-community/windows_exporter/cmd/windows_exporter] function to the service manager. + ExitCodeCh = make(chan int) + + // StopCh is a channel to send a signal to the service manager that the service is stopping. + StopCh = make(chan struct{}) +) + +//nolint:gochecknoinits // An init function is required to communicate with the Windows service manager early in the program. +func init() { + var err error + + IsService, err = svc.IsWindowsService() + if err != nil { + if err := logToEventToLog(windows.EVENTLOG_ERROR_TYPE, fmt.Sprintf("failed to detect service: %v", err)); err != nil { + os.Exit(2) + } + + os.Exit(1) + } + + if !IsService { + return + } + + if err := logToEventToLog(windows.EVENTLOG_INFORMATION_TYPE, "attempting to start exporter service"); err != nil { + os.Exit(2) + } + + go func() { + if err := svc.Run(serviceName, &windowsExporterService{}); err != nil { + _ = logToEventToLog(windows.EVENTLOG_ERROR_TYPE, fmt.Sprintf("failed to start service: %v", err)) + } + }() +} diff --git a/internal/windowsservice/log.go b/internal/windowsservice/log.go new file mode 100644 index 000000000..353b04f45 --- /dev/null +++ b/internal/windowsservice/log.go @@ -0,0 +1,44 @@ +// Copyright 2024 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. + +//go:build windows + +package windowsservice + +import ( + "fmt" + + wineventlog "github.com/prometheus-community/windows_exporter/internal/log/eventlog" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc/eventlog" +) + +// logToEventToLog logs a message to the Windows event log. +func logToEventToLog(eType uint16, msg string) error { + eventLog, err := eventlog.Open("windows_exporter") + if err != nil { + return fmt.Errorf("failed to open event log: %w", err) + } + defer func(eventLog *eventlog.Log) { + _ = eventLog.Close() + }(eventLog) + + p, err := windows.UTF16PtrFromString(msg) + if err != nil { + return fmt.Errorf("error convert string to UTF-16: %w", err) + } + + ss := []*uint16{p, nil, nil, nil, nil, nil, nil, nil, nil} + + return windows.ReportEvent(eventLog.Handle, eType, 0, wineventlog.NeLogOemCode, 0, 9, 0, &ss[0], nil) +} diff --git a/internal/windowsservice/windowsservice.go b/internal/windowsservice/windowsservice.go index 106b27bd4..0be685b0c 100644 --- a/internal/windowsservice/windowsservice.go +++ b/internal/windowsservice/windowsservice.go @@ -11,18 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -// windowsservice allows us to initiate Time Sensitive components (Like registering the windows service) as early as possible in the startup process //go:build windows package windowsservice import ( "fmt" - "os" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" - "golang.org/x/sys/windows/svc/eventlog" ) const ( @@ -31,83 +28,37 @@ const ( type windowsExporterService struct{} +// Execute is the entry point for the Windows service manager. func (s *windowsExporterService) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { - const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown changes <- svc.Status{State: svc.StartPending} - changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} for { select { case exitCodeCh := <-ExitCodeCh: + // Stop the service if an exit code from the main function is received. changes <- svc.Status{State: svc.StopPending} return true, uint32(exitCodeCh) case c := <-r: + // Handle the service control request. switch c.Cmd { case svc.Interrogate: changes <- c.CurrentStatus case svc.Stop, svc.Shutdown: + // Stop the service if a stop or shutdown request is received. _ = logToEventToLog(windows.EVENTLOG_INFORMATION_TYPE, "service stop received") changes <- svc.Status{State: svc.StopPending} - return false, 0 + // Send a signal to the main function to stop the service. + StopCh <- struct{}{} + + // Wait for the main function to stop the service. + return false, uint32(<-ExitCodeCh) default: _ = logToEventToLog(windows.EVENTLOG_ERROR_TYPE, fmt.Sprintf("unexpected control request #%d", c)) } } } } - -var ( - IsService bool - ExitCodeCh = make(chan int) - StopCh = make(chan struct{}) -) - -//nolint:gochecknoinits -func init() { - var err error - - IsService, err = svc.IsWindowsService() - if err != nil { - err = logToEventToLog(windows.EVENTLOG_ERROR_TYPE, fmt.Sprintf("Failed to detect service: %v", err)) - if err != nil { - os.Exit(2) - } - - os.Exit(1) - } - - if IsService { - err = logToEventToLog(windows.EVENTLOG_INFORMATION_TYPE, "Attempting to start exporter service") - - go func() { - err = svc.Run(serviceName, &windowsExporterService{}) - if err != nil { - _ = logToEventToLog(windows.EVENTLOG_ERROR_TYPE, fmt.Sprintf("Failed to start service: %v", err)) - } - - StopCh <- struct{}{} - }() - } -} - -func logToEventToLog(eType uint16, msg string) error { - eventLog, err := eventlog.Open("windows_exporter") - if err != nil { - return fmt.Errorf("failed to open event log: %w", err) - } - defer func(eventLog *eventlog.Log) { - _ = eventLog.Close() - }(eventLog) - - p, err := windows.UTF16PtrFromString(msg) - if err != nil { - return fmt.Errorf("error convert string to UTF-16: %w", err) - } - - ss := []*uint16{p, nil, nil, nil, nil, nil, nil, nil, nil} - - return windows.ReportEvent(eventLog.Handle, eType, 0, 3299, 0, 9, 0, &ss[0], nil) -}