diff --git a/receiver/windowsservicereceiver/README.md b/receiver/windowsservicereceiver/README.md index 42d5f01a8975..9ed9295e590b 100644 --- a/receiver/windowsservicereceiver/README.md +++ b/receiver/windowsservicereceiver/README.md @@ -16,26 +16,25 @@ The Windows Service Receiver is a receiver for scraping information about servic ## Getting Started -By default the Windows Service Receiver will attempt to identify and monitor the status of all specified services on the host machine. - This behavior can be changed by a user setting the `monitor_all` field to true which will tell the receiver to attempt to build a list - and monitor the status of all services present on the host machine. The collection interval for the receiver may also be modified by - specifying a value. Default collection interval is every one minute. +By default the Windows Service Receiver will attempt to identify and monitor the status of all specified services on the host machine. If the `include_services` field is present but empty the receiver will assume that the user wishes to monitor _ALL_ services present on a machine. Should you wish to monitor a majority of services (but not all) an optional `exclude_services` services field may be included with a list of services you do not wish to see metrics from. An example of monitoring three services on a host: ```yaml windowsservice: collection_interval: # default = 1m - services: + include_services: - service1 - service2 - service3 ... ``` -The case where you wish to monitor all services present on a host machine: +The case where you wish to monitor all services present on a host machine, except for `service3`: ```yaml windowsservice: collection_interval: # default = 1m - monitor_all: true + include_services: + exclude_services: + - service3 ... ``` diff --git a/receiver/windowsservicereceiver/config.go b/receiver/windowsservicereceiver/config.go index 3faf6d7449e6..db01578aa68e 100644 --- a/receiver/windowsservicereceiver/config.go +++ b/receiver/windowsservicereceiver/config.go @@ -10,8 +10,8 @@ import ( // Config defines configuration for windowsservice receiver. type Config struct { scraperhelper.ControllerConfig `mapstructure:",squash"` - Services []string `mapstructure:"services"` // user provided list of services to monitor with receiver - MonitorAll bool `mapstructure:"monitor_all"` // monitor all services on host machine. supercedes services + IncludeServices []string `mapstructure:"include_services"` // user provided list of services to monitor with receiver + ExcludeServices []string `mapstructure:"exclude_services"` // user provided list of services to be excluded } // Validate checks the receiver configuration is valid diff --git a/receiver/windowsservicereceiver/go.mod b/receiver/windowsservicereceiver/go.mod index 89aee6f6c4fe..fe662ef05ee9 100644 --- a/receiver/windowsservicereceiver/go.mod +++ b/receiver/windowsservicereceiver/go.mod @@ -1,6 +1,6 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/windowsservicereceiver -go 1.23.1 +go 1.22.0 require ( github.com/google/go-cmp v0.6.0 diff --git a/receiver/windowsservicereceiver/winapi.go b/receiver/windowsservicereceiver/winapi.go new file mode 100644 index 000000000000..6f729980a190 --- /dev/null +++ b/receiver/windowsservicereceiver/winapi.go @@ -0,0 +1,51 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +//go:build windows + +package windowsservicereceiver + +import ( + "syscall" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc/mgr" +) + +type Manager struct { + Handle windows.Handle +} + +func SCConnect() (*Manager, error) { + var s *uint16 + + h, err := windows.OpenSCManager(s, nil, windows.GENERIC_READ) + if err != nil { + return nil, err + } + + if err != nil { + return nil, err + } + + return &Manager{ + Handle: h, + }, nil +} + +func (m *Manager) Disconnect() error { + return windows.CloseServiceHandle(m.Handle) +} + +func (m *Manager) OpenService(sName string) (*mgr.Service, error) { + ptr, err := syscall.UTF16PtrFromString(sName) + if err != nil { + return nil, err + } + + h, err := windows.OpenService(m.Handle, ptr, windows.GENERIC_READ) + if err != nil { + return nil, err + } + + return &mgr.Service{Name: sName, Handle: h}, nil +} diff --git a/receiver/windowsservicereceiver/windowsservice.go b/receiver/windowsservicereceiver/windowsservice.go index a81a4e0b0068..e6f1617d65aa 100644 --- a/receiver/windowsservicereceiver/windowsservice.go +++ b/receiver/windowsservicereceiver/windowsservice.go @@ -5,34 +5,29 @@ package windowsservicereceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/windowsservicereceiver" import ( - "unsafe" - - "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc/mgr" ) // represents the service struct before we have converted it into a metric -type Service struct { +type ServiceStatus struct { ServiceStatus uint32 StartType uint32 - Name string service *mgr.Service } -func GetService(sname string) (*Service, error) { - manager, err := mgr.Connect() - defer manager.Disconnect() +func GetService(sname string) (*ServiceStatus, error) { + m, err := SCConnect() + defer m.Disconnect() if err != nil { return nil, err } - service, err := manager.OpenService(sname) + service, err := m.OpenService(sname) if err != nil { return nil, err } - s := Service{ - Name: sname, + s := ServiceStatus{ service: service, } @@ -49,33 +44,17 @@ func GetService(sname string) (*Service, error) { } // populates fields from service status query -// -// docs for the functions used are found here -// https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-queryservicestatusex -func (s *Service) getStatus() error { - // we need to do this in order to get the size of the buffer we need to actually read the status - var bytesNeeded uint32 - - if err := windows.QueryServiceStatusEx(s.service.Handle, windows.SC_STATUS_PROCESS_INFO, nil, 0, &bytesNeeded); err != windows.ERROR_INSUFFICIENT_BUFFER { - return err - } - - // allocate our buffer with this fun little trick - buf := make([]byte, bytesNeeded) // make an empty buffer of the required size - // remember: unsafe.Pointer() is basically like void * in C, so this is probably pretty sketchy. - lp := (*windows.SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buf[0])) - - // the second time around we know our required buffer (bytesRemaining is set to [bytes needed to read status] - [bytes of buffer supplied]) - if err := windows.QueryServiceStatusEx(s.service.Handle, windows.SC_STATUS_PROCESS_INFO, &buf[0], uint32(len(buf)), &bytesNeeded); err != nil { +func (s *ServiceStatus) getStatus() error { + st, err := s.service.Query() + if err != nil { return err } - - s.ServiceStatus = lp.CurrentState + s.ServiceStatus = uint32(st.State) return nil } // popualtes fields from service config query -func (s *Service) getConfig() error { +func (s *ServiceStatus) getConfig() error { c, err := s.service.Config() if err != nil { return err