1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,91 @@
# Windows Services Input Plugin
Reports information about Windows service status.
Monitoring some services may require running Telegraf with administrator
privileges.
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Input plugin to report Windows services info.
# This plugin ONLY supports Windows
[[inputs.win_services]]
## Names of the services to monitor. Leave empty to monitor all the available
## services on the host. Globs accepted. Case insensitive.
service_names = [
"LanmanServer",
"TermService",
"Win*",
]
# optional, list of service names to exclude
excluded_service_names = ['WinRM']
```
## Metrics
- win_services
- state : integer
- startup_mode : integer
The `state` field can have the following values:
- 1 - stopped
- 2 - start pending
- 3 - stop pending
- 4 - running
- 5 - continue pending
- 6 - pause pending
- 7 - paused
The `startup_mode` field can have the following values:
- 0 - boot start
- 1 - system start
- 2 - auto start
- 3 - demand start
- 4 - disabled
### Tags
- All measurements have the following tags:
- service_name
- display_name
## Example Output
```text
win_services,host=WIN2008R2H401,display_name=Server,service_name=LanmanServer state=4i,startup_mode=2i 1500040669000000000
win_services,display_name=Remote\ Desktop\ Services,service_name=TermService,host=WIN2008R2H401 state=1i,startup_mode=3i 1500040669000000000
```
### TICK Scripts
A sample TICK script for a notification about a not running service. It sends a
notification whenever any service changes its state to be not _running_ and when
it changes that state back to _running_. The notification is sent via an HTTP
POST call.
```shell
stream
|from()
.database('telegraf')
.retentionPolicy('autogen')
.measurement('win_services')
.groupBy('host','service_name')
|alert()
.crit(lambda: "state" != 4)
.stateChangesOnly()
.message('Service {{ index .Tags "service_name" }} on Host {{ index .Tags "host" }} is in state {{ index .Fields "state" }} ')
.post('http://localhost:666/alert/service')
```

View file

@ -0,0 +1,13 @@
# Input plugin to report Windows services info.
# This plugin ONLY supports Windows
[[inputs.win_services]]
## Names of the services to monitor. Leave empty to monitor all the available
## services on the host. Globs accepted. Case insensitive.
service_names = [
"LanmanServer",
"TermService",
"Win*",
]
# optional, list of service names to exclude
excluded_service_names = ['WinRM']

View file

@ -0,0 +1,248 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build windows
package win_services
import (
_ "embed"
"errors"
"fmt"
"io/fs"
"strings"
"syscall"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type WinServices struct {
ServiceNames []string `toml:"service_names"`
ServiceNamesExcluded []string `toml:"excluded_service_names"`
Log telegraf.Logger `toml:"-"`
mgrProvider managerProvider
servicesFilter filter.Filter
}
// winService provides interface for svc.Service
type winService interface {
Close() error
Config() (mgr.Config, error)
Query() (svc.Status, error)
}
// managerProvider sets interface for acquiring manager instance, like mgr.Mgr
type managerProvider interface {
connect() (winServiceManager, error)
}
// winServiceManager provides interface for mgr.Mgr
type winServiceManager interface {
disconnect() error
openService(name string) (winService, error)
listServices() ([]string, error)
}
type serviceInfo struct {
ServiceName string
DisplayName string
State int
StartUpMode int
}
func (*WinServices) SampleConfig() string {
return sampleConfig
}
func (m *WinServices) Init() error {
// For case insensitive comparison (see issue #8796) we need to transform the services
// to lowercase
servicesInclude := make([]string, 0, len(m.ServiceNames))
for _, s := range m.ServiceNames {
servicesInclude = append(servicesInclude, strings.ToLower(s))
}
servicesExclude := make([]string, 0, len(m.ServiceNamesExcluded))
for _, s := range m.ServiceNamesExcluded {
servicesExclude = append(servicesExclude, strings.ToLower(s))
}
f, err := filter.NewIncludeExcludeFilter(servicesInclude, servicesExclude)
if err != nil {
return err
}
m.servicesFilter = f
return nil
}
func (m *WinServices) Gather(acc telegraf.Accumulator) error {
scmgr, err := m.mgrProvider.connect()
if err != nil {
return fmt.Errorf("could not open service manager: %w", err)
}
defer scmgr.disconnect()
serviceNames, err := m.listServices(scmgr)
if err != nil {
return err
}
for _, srvName := range serviceNames {
service, err := collectServiceInfo(scmgr, srvName)
if err != nil {
if isPermission(err) {
m.Log.Debug(err.Error())
} else {
m.Log.Error(err.Error())
}
continue
}
tags := map[string]string{
"service_name": service.ServiceName,
}
// display name could be empty, but still valid service
if len(service.DisplayName) > 0 {
tags["display_name"] = service.DisplayName
}
fields := map[string]interface{}{
"state": service.State,
"startup_mode": service.StartUpMode,
}
acc.AddFields("win_services", fields, tags)
}
return nil
}
// listServices returns a list of services to gather.
func (m *WinServices) listServices(scmgr winServiceManager) ([]string, error) {
names, err := scmgr.listServices()
if err != nil {
return nil, fmt.Errorf("could not list services: %w", err)
}
var services []string
for _, name := range names {
// Compare case-insensitive. Use lowercase as we already converted the filter to use it.
n := strings.ToLower(name)
if m.servicesFilter.Match(n) {
services = append(services, name)
}
}
return services, nil
}
func isPermission(err error) bool {
var serviceErr *serviceError
if errors.As(err, &serviceErr) {
return errors.Is(serviceErr, fs.ErrPermission)
}
return false
}
// collectServiceInfo gathers info about a service.
func collectServiceInfo(scmgr winServiceManager, serviceName string) (*serviceInfo, error) {
srv, err := scmgr.openService(serviceName)
if err != nil {
return nil, &serviceError{
message: "could not open service",
service: serviceName,
err: err,
}
}
defer srv.Close()
srvStatus, err := srv.Query()
if err != nil {
return nil, &serviceError{
message: "could not query service",
service: serviceName,
err: err,
}
}
srvCfg, err := srv.Config()
if err != nil {
return nil, &serviceError{
message: "could not get config of service",
service: serviceName,
err: err,
}
}
serviceInfo := &serviceInfo{
ServiceName: serviceName,
DisplayName: srvCfg.DisplayName,
StartUpMode: int(srvCfg.StartType),
State: int(srvStatus.State),
}
return serviceInfo, nil
}
type serviceError struct {
message string
service string
err error
}
func (e *serviceError) Error() string {
return fmt.Sprintf("%s: %q: %v", e.message, e.service, e.err)
}
// winSvcMgr is wrapper for mgr.Mgr implementing winServiceManager interface
type winSvcMgr struct {
realMgr *mgr.Mgr
}
func (m *winSvcMgr) disconnect() error {
return m.realMgr.Disconnect()
}
func (m *winSvcMgr) openService(name string) (winService, error) {
serviceName, err := syscall.UTF16PtrFromString(name)
if err != nil {
return nil, fmt.Errorf("cannot convert service name %q: %w", name, err)
}
h, err := windows.OpenService(m.realMgr.Handle, serviceName, windows.GENERIC_READ)
if err != nil {
return nil, err
}
return &mgr.Service{Name: name, Handle: h}, nil
}
func (m *winSvcMgr) listServices() ([]string, error) {
return m.realMgr.ListServices()
}
// mgProvider is an implementation of WinServiceManagerProvider interface returning winSvcMgr
type mgProvider struct {
}
func (*mgProvider) connect() (winServiceManager, error) {
h, err := windows.OpenSCManager(nil, nil, windows.GENERIC_READ)
if err != nil {
return nil, err
}
scmgr := &mgr.Mgr{Handle: h}
return &winSvcMgr{scmgr}, nil
}
func init() {
inputs.Add("win_services", func() telegraf.Input {
return &WinServices{
mgrProvider: &mgProvider{},
}
})
}

View file

@ -0,0 +1,78 @@
//go:build windows
// these tests must be run under administrator account
package win_services
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
var invalidServices = []string{"XYZ1@", "ZYZ@", "SDF_@#"}
var knownServices = []string{"LanmanServer", "TermService"}
func TestListIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
provider := &mgProvider{}
scmgr, err := provider.connect()
require.NoError(t, err)
defer func() {
err := scmgr.disconnect()
require.NoError(t, err)
}()
winServices := &WinServices{
ServiceNames: knownServices,
}
require.NoError(t, winServices.Init())
services, err := winServices.listServices(scmgr)
require.NoError(t, err)
require.Len(t, services, 2, "Different number of services")
require.Equal(t, services[0], knownServices[0])
require.Equal(t, services[1], knownServices[1])
}
func TestEmptyListIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
provider := &mgProvider{}
scmgr, err := provider.connect()
require.NoError(t, err)
defer func() {
err := scmgr.disconnect()
require.NoError(t, err)
}()
winServices := &WinServices{
ServiceNames: make([]string, 0),
}
require.NoError(t, winServices.Init())
services, err := winServices.listServices(scmgr)
require.NoError(t, err)
require.Condition(t, func() bool { return len(services) > 20 }, "Too few service")
}
func TestGatherErrorsIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
ws := &WinServices{
Log: testutil.Logger{},
ServiceNames: invalidServices,
mgrProvider: &mgProvider{},
}
require.NoError(t, ws.Init())
require.Len(t, ws.ServiceNames, 3, "Different number of services")
var acc testutil.Accumulator
require.NoError(t, ws.Gather(&acc))
require.Len(t, acc.Errors, 3, "There should be 3 errors after gather")
}

View file

@ -0,0 +1,33 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build !windows
package win_services
import (
_ "embed"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type WinServices struct {
Log telegraf.Logger `toml:"-"`
}
func (*WinServices) SampleConfig() string { return sampleConfig }
func (w *WinServices) Init() error {
w.Log.Warn("Current platform is not supported")
return nil
}
func (*WinServices) Gather(telegraf.Accumulator) error { return nil }
func init() {
inputs.Add("win_services", func() telegraf.Input {
return &WinServices{}
})
}

View file

@ -0,0 +1,240 @@
//go:build windows
package win_services
import (
"bytes"
"errors"
"fmt"
"log"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
"github.com/influxdata/telegraf/testutil"
)
// testData is DD wrapper for unit testing of WinServices
type testData struct {
// collection that will be returned in listServices if service array passed into WinServices constructor is empty
queryServiceList []string
mgrConnectError error
mgrListServicesError error
services []serviceTestInfo
}
type serviceTestInfo struct {
serviceOpenError error
serviceQueryError error
serviceConfigError error
serviceName string
displayName string
state int
startUpMode int
}
type FakeSvcMgr struct {
testData testData
}
func (*FakeSvcMgr) disconnect() error {
return nil
}
func (m *FakeSvcMgr) openService(name string) (winService, error) {
for _, s := range m.testData.services {
if s.serviceName == name {
if s.serviceOpenError != nil {
return nil, s.serviceOpenError
}
return &fakeWinSvc{s}, nil
}
}
return nil, fmt.Errorf("cannot find service %q", name)
}
func (m *FakeSvcMgr) listServices() ([]string, error) {
if m.testData.mgrListServicesError != nil {
return nil, m.testData.mgrListServicesError
}
return m.testData.queryServiceList, nil
}
type FakeMgProvider struct {
testData testData
}
func (m *FakeMgProvider) connect() (winServiceManager, error) {
if m.testData.mgrConnectError != nil {
return nil, m.testData.mgrConnectError
}
return &FakeSvcMgr{m.testData}, nil
}
type fakeWinSvc struct {
testData serviceTestInfo
}
func (*fakeWinSvc) Close() error {
return nil
}
func (m *fakeWinSvc) Config() (mgr.Config, error) {
if m.testData.serviceConfigError != nil {
return mgr.Config{}, m.testData.serviceConfigError
}
return mgr.Config{
ServiceType: 0,
StartType: uint32(m.testData.startUpMode),
ErrorControl: 0,
BinaryPathName: "",
LoadOrderGroup: "",
TagId: 0,
Dependencies: nil,
ServiceStartName: m.testData.serviceName,
DisplayName: m.testData.displayName,
Password: "",
Description: "",
}, nil
}
func (m *fakeWinSvc) Query() (svc.Status, error) {
if m.testData.serviceQueryError != nil {
return svc.Status{}, m.testData.serviceQueryError
}
return svc.Status{
State: svc.State(m.testData.state),
Accepts: 0,
CheckPoint: 0,
WaitHint: 0,
}, nil
}
var testErrors = []testData{
{nil, errors.New("fake mgr connect error"), nil, nil},
{nil, nil, errors.New("fake mgr list services error"), nil},
{[]string{"Fake service 1", "Fake service 2", "Fake service 3"}, nil, nil, []serviceTestInfo{
{errors.New("fake srv open error"), nil, nil, "Fake service 1", "", 0, 0},
{nil, errors.New("fake srv query error"), nil, "Fake service 2", "", 0, 0},
{nil, nil, errors.New("fake srv config error"), "Fake service 3", "", 0, 0},
}},
{[]string{"Fake service 1"}, nil, nil, []serviceTestInfo{
{errors.New("fake srv open error"), nil, nil, "Fake service 1", "", 0, 0},
}},
}
func TestMgrErrors(t *testing.T) {
// mgr.connect error
winServices := &WinServices{
Log: testutil.Logger{},
mgrProvider: &FakeMgProvider{testErrors[0]},
}
var acc1 testutil.Accumulator
err := winServices.Gather(&acc1)
require.Error(t, err)
require.Contains(t, err.Error(), testErrors[0].mgrConnectError.Error())
// mgr.listServices error
winServices = &WinServices{
Log: testutil.Logger{},
mgrProvider: &FakeMgProvider{testErrors[1]},
}
var acc2 testutil.Accumulator
err = winServices.Gather(&acc2)
require.Error(t, err)
require.Contains(t, err.Error(), testErrors[1].mgrListServicesError.Error())
// mgr.listServices error 2
winServices = &WinServices{
Log: testutil.Logger{},
ServiceNames: []string{"Fake service 1"},
mgrProvider: &FakeMgProvider{testErrors[3]},
}
err = winServices.Init()
require.NoError(t, err)
var acc3 testutil.Accumulator
buf := &bytes.Buffer{}
log.SetOutput(buf)
require.NoError(t, winServices.Gather(&acc3))
require.Contains(t, buf.String(), testErrors[2].services[0].serviceOpenError.Error())
}
func TestServiceErrors(t *testing.T) {
winServices := &WinServices{
Log: testutil.Logger{},
mgrProvider: &FakeMgProvider{testErrors[2]},
}
err := winServices.Init()
require.NoError(t, err)
var acc1 testutil.Accumulator
buf := &bytes.Buffer{}
log.SetOutput(buf)
require.NoError(t, winServices.Gather(&acc1))
// open service error
require.Contains(t, buf.String(), testErrors[2].services[0].serviceOpenError.Error())
// query service error
require.Contains(t, buf.String(), testErrors[2].services[1].serviceQueryError.Error())
// config service error
require.Contains(t, buf.String(), testErrors[2].services[2].serviceConfigError.Error())
}
var testSimpleData = []testData{
{[]string{"Service 1", "Service 2"}, nil, nil, []serviceTestInfo{
{nil, nil, nil, "Service 1", "Fake service 1", 1, 2},
{nil, nil, nil, "Service 2", "Fake service 2", 1, 2},
}},
}
func TestGatherContainsTag(t *testing.T) {
winServices := &WinServices{
Log: testutil.Logger{},
ServiceNames: []string{"Service*"},
mgrProvider: &FakeMgProvider{testSimpleData[0]},
}
err := winServices.Init()
require.NoError(t, err)
var acc1 testutil.Accumulator
require.NoError(t, winServices.Gather(&acc1))
require.Empty(t, acc1.Errors, "There should be no errors after gather")
for _, s := range testSimpleData[0].services {
fields := make(map[string]interface{})
tags := make(map[string]string)
fields["state"] = s.state
fields["startup_mode"] = s.startUpMode
tags["service_name"] = s.serviceName
tags["display_name"] = s.displayName
acc1.AssertContainsTaggedFields(t, "win_services", fields, tags)
}
}
func TestExcludingNamesTag(t *testing.T) {
winServices := &WinServices{
Log: testutil.Logger{},
ServiceNamesExcluded: []string{"Service*"},
mgrProvider: &FakeMgProvider{testSimpleData[0]},
}
err := winServices.Init()
require.NoError(t, err)
var acc1 testutil.Accumulator
require.NoError(t, winServices.Gather(&acc1))
for _, s := range testSimpleData[0].services {
fields := make(map[string]interface{})
tags := make(map[string]string)
fields["state"] = s.state
fields["startup_mode"] = s.startUpMode
tags["service_name"] = s.serviceName
tags["display_name"] = s.displayName
acc1.AssertDoesNotContainsTaggedFields(t, "win_services", fields, tags)
}
}