From 0db2520c1518822b806e1cdce4d806a1033463f3 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Mon, 10 Jul 2017 15:08:26 +0200 Subject: [PATCH 01/44] Initial win_services plugin code: a hardcoded service reporting random state and fixed startup config --- plugins/inputs/all/all.go | 1 + plugins/inputs/win_services/README.md | 336 ++++++++++++++++++ plugins/inputs/win_services/win_services.go | 88 +++++ .../win_services/win_services_notwindows.go | 3 + .../inputs/win_services/win_services_test.go | 6 + 5 files changed, 434 insertions(+) create mode 100644 plugins/inputs/win_services/README.md create mode 100644 plugins/inputs/win_services/win_services.go create mode 100644 plugins/inputs/win_services/win_services_notwindows.go create mode 100644 plugins/inputs/win_services/win_services_test.go diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 0796a8422aafb..f7f673249a735 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -86,6 +86,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/varnish" _ "github.com/influxdata/telegraf/plugins/inputs/webhooks" _ "github.com/influxdata/telegraf/plugins/inputs/win_perf_counters" + _ "github.com/influxdata/telegraf/plugins/inputs/win_services" _ "github.com/influxdata/telegraf/plugins/inputs/zfs" _ "github.com/influxdata/telegraf/plugins/inputs/zookeeper" ) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md new file mode 100644 index 0000000000000..a19f4c8b5ab38 --- /dev/null +++ b/plugins/inputs/win_services/README.md @@ -0,0 +1,336 @@ +# win_services readme + +The way this plugin works is that on load of Telegraf, +the plugin will be handed configuration from Telegraf. +This configuration is parsed and then tested for validity such as +if the Object, Instance and Counter existing. +If it does not match at startup, it will not be fetched. +Exceptions to this are in cases where you query for all instances "*". +By default the plugin does not return _Total +when it is querying for all (*) as this is redundant. + +## Basics + +The examples contained in this file have been found on the internet +as counters used when performance monitoring + Active Directory and IIS in perticular. + There are a lot other good objects to monitor, if you know what to look for. + This file is likely to be updated in the future with more examples for + useful configurations for separate scenarios. + +### Plugin wide + +Plugin wide entries are underneath `[[inputs.win_perf_counters]]`. + +#### PrintValid + +Bool, if set to `true` will print out all matching performance objects. + +Example: +`PrintValid=true` + +#### PreVistaSupport + +Bool, if set to `true` will use the localized PerfCounter interface that is present before Vista for backwards compatability. + +It is recommended NOT to use this on OSes starting with Vista and newer because it requires more configuration to use this than the newer interface present since Vista. + +Example for Windows Server 2003, this would be set to true: +`PreVistaSupport=true` + +### Object + +See Entry below. + +### Entry +A new configuration entry consists of the TOML header to start with, +`[[inputs.win_perf_counters.object]]`. +This must follow before other plugins configuration, +beneath the main win_perf_counters entry, `[[inputs.win_perf_counters]]`. + +Following this is 3 required key/value pairs and the three optional parameters and their usage. + +#### ObjectName +**Required** + +ObjectName is the Object to query for, like Processor, DirectoryServices, LogicalDisk or similar. + +Example: `ObjectName = "LogicalDisk"` + +#### Instances +**Required** + +Instances (this is an array) is the instances of a counter you would like returned, +it can be one or more values. + +Example, `Instances = ["C:","D:","E:"]` will return only for the instances +C:, D: and E: where relevant. To get all instances of a Counter, use ["*"] only. +By default any results containing _Total are stripped, +unless this is specified as the wanted instance. +Alternatively see the option IncludeTotal below. + +Some Objects does not have instances to select from at all, +here only one option is valid if you want data back, +and that is to specify `Instances = ["------"]`. + +#### Counters +**Required** + +Counters (this is an array) is the counters of the ObjectName +you would like returned, it can also be one or more values. + +Example: `Counters = ["% Idle Time", "% Disk Read Time", "% Disk Write Time"]` +This must be specified for every counter you want the results of, +it is not possible to ask for all counters in the ObjectName. + +#### Measurement +*Optional* + +This key is optional, if it is not set it will be win_perf_counters. +In InfluxDB this is the key by which the returned data is stored underneath, +so for ordering your data in a good manner, +this is a good key to set with where you want your IIS and Disk results stored, +separate from Processor results. + +Example: `Measurement = "win_disk" + +#### IncludeTotal +*Optional* + +This key is optional, it is a simple bool. +If it is not set to true or included it is treated as false. +This key only has an effect if Instances is set to "*" +and you would also like all instances containg _Total returned, +like "_Total", "0,_Total" and so on where applicable +(Processor Information is one example). + +#### WarnOnMissing +*Optional* + +This key is optional, it is a simple bool. +If it is not set to true or included it is treated as false. +This only has an effect on the first execution of the plugin, +it will print out any ObjectName/Instance/Counter combinations +asked for that does not match. Useful when debugging new configurations. + +#### FailOnMissing +*Internal* + +This key should not be used, it is for testing purposes only. +It is a simple bool, if it is not set to true or included this is treaded as false. +If this is set to true, the plugin will abort and end prematurely +if any of the combinations of ObjectName/Instances/Counters are invalid. + +## Examples + +### Generic Queries +``` + + [[inputs.win_perf_counters.object]] + # Processor usage, alternative to native, reports on a per core. + ObjectName = "Processor" + Instances = ["*"] + Counters = ["% Idle Time", "% Interrupt Time", "% Privileged Time", "% User Time", "% Processor Time"] + Measurement = "win_cpu" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # Disk times and queues + ObjectName = "LogicalDisk" + Instances = ["*"] + Counters = ["% Idle Time", "% Disk Time","% Disk Read Time", "% Disk Write Time", "% User Time", "Current Disk Queue Length"] + Measurement = "win_disk" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + ObjectName = "System" + Counters = ["Context Switches/sec","System Calls/sec", "Processor Queue Length"] + Instances = ["------"] + Measurement = "win_system" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # Example query where the Instance portion must be removed to get data back, such as from the Memory object. + ObjectName = "Memory" + Counters = ["Available Bytes","Cache Faults/sec","Demand Zero Faults/sec","Page Faults/sec","Pages/sec","Transition Faults/sec","Pool Nonpaged Bytes","Pool Paged Bytes"] + Instances = ["------"] # Use 6 x - to remove the Instance bit from the query. + Measurement = "win_mem" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # more counters for the Network Interface Object can be found at + # https://msdn.microsoft.com/en-us/library/ms803962.aspx + ObjectName = "Network Interface" + Counters = ["Bytes Received/sec","Bytes Sent/sec","Packets Received/sec","Packets Sent/sec"] + Instances = ["*"] # Use 6 x - to remove the Instance bit from the query. + Measurement = "win_net" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +``` + +### Active Directory Domain Controller +``` + [[inputs.win_perf_counters.object]] + ObjectName = "DirectoryServices" + Instances = ["*"] + Counters = ["Base Searches/sec","Database adds/sec","Database deletes/sec","Database modifys/sec","Database recycles/sec","LDAP Client Sessions","LDAP Searches/sec","LDAP Writes/sec"] + Measurement = "win_ad" # Set an alternative measurement to win_perf_counters if wanted. + #Instances = [""] # Gathers all instances by default, specify to only gather these + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + ObjectName = "Security System-Wide Statistics" + Instances = ["*"] + Counters = ["NTLM Authentications","Kerberos Authentications","Digest Authentications"] + Measurement = "win_ad" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + ObjectName = "Database" + Instances = ["*"] + Counters = ["Database Cache % Hit","Database Cache Page Fault Stalls/sec","Database Cache Page Faults/sec","Database Cache Size"] + Measurement = "win_db" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +``` + +### DFS Namespace + Domain Controllers +``` + [[inputs.win_perf_counters.object]] + # AD, DFS N, Useful if the server hosts a DFS Namespace or is a Domain Controller + ObjectName = "DFS Namespace Service Referrals" + Instances = ["*"] + Counters = ["Requests Processed","Requests Failed","Avg. Response Time"] + Measurement = "win_dfsn" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + #WarnOnMissing = false # Print out when the performance counter is missing, either of object, counter or instance. +``` + + +### DFS Replication + Domain Controllers +``` + [[inputs.win_perf_counters.object]] + # AD, DFS R, Useful if the server hosts a DFS Replication folder or is a Domain Controller + ObjectName = "DFS Replication Service Volumes" + Instances = ["*"] + Counters = ["Data Lookups","Database Commits"] + Measurement = "win_dfsr" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + #WarnOnMissing = false # Print out when the performance counter is missing, either of object, counter or instance. +``` + + +### DNS Server + Domain Controllers +``` + [[inputs.win_perf_counters.object]] + ObjectName = "DNS" + Counters = ["Dynamic Update Received","Dynamic Update Rejected","Recursive Queries","Recursive Queries Failure","Secure Update Failure","Secure Update Received","TCP Query Received","TCP Response Sent","UDP Query Received","UDP Response Sent","Total Query Received","Total Response Sent"] + Instances = ["------"] + Measurement = "win_dns" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +``` + +### IIS / ASP.NET +``` + [[inputs.win_perf_counters.object]] + # HTTP Service request queues in the Kernel before being handed over to User Mode. + ObjectName = "HTTP Service Request Queues" + Instances = ["*"] + Counters = ["CurrentQueueSize","RejectedRequests"] + Measurement = "win_http_queues" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # IIS, ASP.NET Applications + ObjectName = "ASP.NET Applications" + Counters = ["Cache Total Entries","Cache Total Hit Ratio","Cache Total Turnover Rate","Output Cache Entries","Output Cache Hits","Output Cache Hit Ratio","Output Cache Turnover Rate","Compilations Total","Errors Total/Sec","Pipeline Instance Count","Requests Executing","Requests in Application Queue","Requests/Sec"] + Instances = ["*"] + Measurement = "win_aspnet_app" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # IIS, ASP.NET + ObjectName = "ASP.NET" + Counters = ["Application Restarts","Request Wait Time","Requests Current","Requests Queued","Requests Rejected"] + Instances = ["*"] + Measurement = "win_aspnet" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # IIS, Web Service + ObjectName = "Web Service" + Counters = ["Get Requests/sec","Post Requests/sec","Connection Attempts/sec","Current Connections","ISAPI Extension Requests/sec"] + Instances = ["*"] + Measurement = "win_websvc" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # Web Service Cache / IIS + ObjectName = "Web Service Cache" + Counters = ["URI Cache Hits %","Kernel: URI Cache Hits %","File Cache Hits %"] + Instances = ["*"] + Measurement = "win_websvc_cache" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +``` + + +### Process +``` + [[inputs.win_perf_counters.object]] + # Process metrics, in this case for IIS only + ObjectName = "Process" + Counters = ["% Processor Time","Handle Count","Private Bytes","Thread Count","Virtual Bytes","Working Set"] + Instances = ["w3wp"] + Measurement = "win_proc" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +``` + + +### .NET Montioring +``` + [[inputs.win_perf_counters.object]] + # .NET CLR Exceptions, in this case for IIS only + ObjectName = ".NET CLR Exceptions" + Counters = ["# of Exceps Thrown / sec"] + Instances = ["w3wp"] + Measurement = "win_dotnet_exceptions" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # .NET CLR Jit, in this case for IIS only + ObjectName = ".NET CLR Jit" + Counters = ["% Time in Jit","IL Bytes Jitted / sec"] + Instances = ["w3wp"] + Measurement = "win_dotnet_jit" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # .NET CLR Loading, in this case for IIS only + ObjectName = ".NET CLR Loading" + Counters = ["% Time Loading"] + Instances = ["w3wp"] + Measurement = "win_dotnet_loading" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # .NET CLR LocksAndThreads, in this case for IIS only + ObjectName = ".NET CLR LocksAndThreads" + Counters = ["# of current logical Threads","# of current physical Threads","# of current recognized threads","# of total recognized threads","Queue Length / sec","Total # of Contentions","Current Queue Length"] + Instances = ["w3wp"] + Measurement = "win_dotnet_locks" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # .NET CLR Memory, in this case for IIS only + ObjectName = ".NET CLR Memory" + Counters = ["% Time in GC","# Bytes in all Heaps","# Gen 0 Collections","# Gen 1 Collections","# Gen 2 Collections","# Induced GC","Allocated Bytes/sec","Finalization Survivors","Gen 0 heap size","Gen 1 heap size","Gen 2 heap size","Large Object Heap size","# of Pinned Objects"] + Instances = ["w3wp"] + Measurement = "win_dotnet_mem" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). + + [[inputs.win_perf_counters.object]] + # .NET CLR Security, in this case for IIS only + ObjectName = ".NET CLR Security" + Counters = ["% Time in RT checks","Stack Walk Depth","Total Runtime Checks"] + Instances = ["w3wp"] + Measurement = "win_dotnet_security" + #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +``` diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go new file mode 100644 index 0000000000000..8e1265c757074 --- /dev/null +++ b/plugins/inputs/win_services/win_services.go @@ -0,0 +1,88 @@ +// +build windows + +package win_services + +import ( + "log" + "math/rand" + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +var sampleConfig = ` + ## This plugin returns by default service state and startup mode + ## See the README file for more examples. + ## Uncomment examples below or write your own as you see fit. If the system + ## being polled for data does not have the Object at startup of the Telegraf + ## agent, it will not be gathered. + ## Settings: + + # Names of services to monitor + Services = [ + "Server" + ] + Measurement = "win_services" + # CustomTagName=Group + # CustomTagValue=alpha +` + +var description = "Input plugin to report Windows services info: name, state, startup mode, hostname" + +type Win_Services struct { + Services []string + Measurement string + CustomTagName string + CustomTagValue string + + configParsed bool +} + +type service struct { + ServiceName string + State int + StartUpMode int +} + + +func (m *Win_Services) Description() string { + return description +} + +func (m *Win_Services) SampleConfig() string { + return sampleConfig +} + +func (m *Win_Services) ParseConfig() error { + log.Printf("win_services: parse config: %v\n", *m) + return nil +} + +func (m *Win_Services) Gather(acc telegraf.Accumulator) error { + // Parse the config once + if !m.configParsed { + err := m.ParseConfig() + m.configParsed = true + if err != nil { + return err + } + } + + fields := make(map[string]interface{}) + tags := make(map[string]string) + tags["service"] = "Server"; + fields["state"] = rand.Int()%3; + fields["startupMode"] = 3; + + measurement := m.Measurement + if measurement == "" { + measurement = "win_services" + } + acc.AddFields(measurement, fields, tags) + + return nil +} + +func init() { + log.Println("win_services: init") + inputs.Add("win_services", func() telegraf.Input { return &Win_Services{} }) +} diff --git a/plugins/inputs/win_services/win_services_notwindows.go b/plugins/inputs/win_services/win_services_notwindows.go new file mode 100644 index 0000000000000..062c11cfc8eed --- /dev/null +++ b/plugins/inputs/win_services/win_services_notwindows.go @@ -0,0 +1,3 @@ +// +build !windows + +package win_services diff --git a/plugins/inputs/win_services/win_services_test.go b/plugins/inputs/win_services/win_services_test.go new file mode 100644 index 0000000000000..a26d210239377 --- /dev/null +++ b/plugins/inputs/win_services/win_services_test.go @@ -0,0 +1,6 @@ +// +build windows + +package win_services + + + From 7400e87e61d08dca5469bb763f51906f8fa5280e Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 11 Jul 2017 22:37:53 +0200 Subject: [PATCH 02/44] Newer golang.org/x/sys for mgr.ListServices --- Godeps_windows | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Godeps_windows b/Godeps_windows index 129fce1a2267b..905c02898ca77 100644 --- a/Godeps_windows +++ b/Godeps_windows @@ -2,7 +2,7 @@ github.com/Microsoft/go-winio ce2922f643c8fd76b46cadc7f404a06282678b34 github.com/StackExchange/wmi f3e2bae1e0cb5aef83e319133eabfee30013a4a5 github.com/go-ole/go-ole be49f7c07711fcb603cff39e1de7c67926dc0ba7 github.com/shirou/w32 3c9377fc6748f222729a8270fe2775d149a249ad -golang.org/x/sys a646d33e2ee3172a661fc09bca23bb4889a41bc8 +golang.org/x/sys 739734461d1c916b6c72a63d7efda2b27edb369f github.com/go-ini/ini 9144852efba7c4daf409943ee90767da62d55438 github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d github.com/pmezard/go-difflib/difflib 792786c7400a136282c1664665ae0a8db921c6c2 From 1d52da8a851e12118b3a64edb5998fa3e4298915 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 11 Jul 2017 22:38:44 +0200 Subject: [PATCH 03/44] Completed basic functionality --- plugins/inputs/win_services/win_services.go | 109 ++++++++++++++------ 1 file changed, 75 insertions(+), 34 deletions(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index 8e1265c757074..cc577f23c6606 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -3,10 +3,11 @@ package win_services import ( - "log" - "math/rand" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" + "golang.org/x/sys/windows/svc/mgr" + "strconv" + "errors" ) var sampleConfig = ` @@ -17,30 +18,28 @@ var sampleConfig = ` ## agent, it will not be gathered. ## Settings: - # Names of services to monitor + # Names of services to monitor. Empty for all Services = [ "Server" ] - Measurement = "win_services" # CustomTagName=Group # CustomTagValue=alpha ` -var description = "Input plugin to report Windows services info: name, state, startup mode, hostname" +var description = "Input plugin to report Windows services info: name, display name, state, startup mode" type Win_Services struct { Services []string - Measurement string CustomTagName string CustomTagValue string - - configParsed bool } -type service struct { +type ServiceInfo struct { ServiceName string + DisplayName string State int StartUpMode int + Error error } @@ -52,37 +51,79 @@ func (m *Win_Services) SampleConfig() string { return sampleConfig } -func (m *Win_Services) ParseConfig() error { - log.Printf("win_services: parse config: %v\n", *m) - return nil -} func (m *Win_Services) Gather(acc telegraf.Accumulator) error { - // Parse the config once - if !m.configParsed { - err := m.ParseConfig() - m.configParsed = true - if err != nil { - return err - } - } - - fields := make(map[string]interface{}) - tags := make(map[string]string) - tags["service"] = "Server"; - fields["state"] = rand.Int()%3; - fields["startupMode"] = 3; - - measurement := m.Measurement - if measurement == "" { - measurement = "win_services" - } - acc.AddFields(measurement, fields, tags) + + serviceInfos, err := listServices(m.Services) + + if err != nil { + return err + } + + for _, service := range serviceInfos { + fields := make(map[string]interface{}) + tags := make(map[string]string) + if service.Error == nil { + fields["displayname"] = service.DisplayName + tags["state"] = strconv.Itoa(service.State) + tags["startupMode"] = strconv.Itoa(service.StartUpMode) + } else { + fields["service"] = service.ServiceName + tags["error"] = service.Error.Error() + + } + acc.AddFields(service.ServiceName, fields, tags) + } return nil } +func listServices(userServices []string) ([]ServiceInfo, error) { + + scmgr, err := mgr.Connect() + if err != nil { + return nil, errors.New("Could not open service manager: " + err.Error()); + } + defer scmgr.Disconnect() + + var serviceNames []string + if len(userServices) == 0 { + //Listing service names from system + serviceNames, err = scmgr.ListServices() + if err != nil { + return nil, errors.New("Could not list services: " + err.Error()); + } + } else { + serviceNames = userServices + } + serviceInfos := make([]ServiceInfo, len(serviceNames)) + + for i, srvName := range serviceNames { + serviceInfos[i].ServiceName = srvName + srv, err := scmgr.OpenService(srvName) + if err != nil { + serviceInfos[i].Error = errors.New("Could not open service: " + err.Error()); + continue + } + srvStatus, err := srv.Query() + if err == nil { + serviceInfos[i].State = int(srvStatus.State) + } else { + serviceInfos[i].Error = errors.New("Could not query service: " + err.Error()); + } + + srvCfg, err := srv.Config() + if err == nil { + serviceInfos[i].DisplayName = srvCfg.DisplayName + serviceInfos[i].StartUpMode = int(srvCfg.StartType) + } else { + serviceInfos[i].Error = errors.New("Could not get service config: " + err.Error()); + } + srv.Close() + } + return serviceInfos, nil +} + func init() { - log.Println("win_services: init") inputs.Add("win_services", func() telegraf.Input { return &Win_Services{} }) } From 3b4df689da153cea66c6b8ce02f3a2b6bb5d941a Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 12 Jul 2017 13:38:36 +0200 Subject: [PATCH 04/44] Marked with TODO --- plugins/inputs/win_perf_counters/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/inputs/win_perf_counters/README.md b/plugins/inputs/win_perf_counters/README.md index 06d9f1b9d5f23..190026743f50d 100644 --- a/plugins/inputs/win_perf_counters/README.md +++ b/plugins/inputs/win_perf_counters/README.md @@ -1,5 +1,7 @@ # win_perf_counters readme +vh: TODO + The way this plugin works is that on load of Telegraf, the plugin will be handed configuration from Telegraf. This configuration is parsed and then tested for validity such as From b67c645081bd97c669044f9eb81f287ff7fa1daa Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 12 Jul 2017 13:41:35 +0200 Subject: [PATCH 05/44] All services log under same measurement Measurement name made configurable Error service is indicated with state -1 --- plugins/inputs/win_services/win_services.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index cc577f23c6606..1abd90799c672 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -30,6 +30,7 @@ var description = "Input plugin to report Windows services info: name, display n type Win_Services struct { Services []string + Measurement string CustomTagName string CustomTagValue string } @@ -60,19 +61,23 @@ func (m *Win_Services) Gather(acc telegraf.Accumulator) error { return err } + if m.Measurement == "" { + m.Measurement = "win_services" + } + for _, service := range serviceInfos { fields := make(map[string]interface{}) tags := make(map[string]string) + tags["service"] = service.ServiceName if service.Error == nil { - fields["displayname"] = service.DisplayName + fields["displayName"] = service.DisplayName tags["state"] = strconv.Itoa(service.State) tags["startupMode"] = strconv.Itoa(service.StartUpMode) } else { - fields["service"] = service.ServiceName - tags["error"] = service.Error.Error() - + fields["error"] = service.Error.Error() + tags["state"] = strconv.Itoa(-1) //indicate error state } - acc.AddFields(service.ServiceName, fields, tags) + acc.AddFields(m.Measurement, fields, tags) } return nil From 017c131c4063619ec4754d422d7a16e5751ab3e9 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 12 Jul 2017 17:37:17 +0200 Subject: [PATCH 06/44] Requirement analysis for windows services --- plugins/inputs/win_services/req_analysis.md | 113 ++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 plugins/inputs/win_services/req_analysis.md diff --git a/plugins/inputs/win_services/req_analysis.md b/plugins/inputs/win_services/req_analysis.md new file mode 100644 index 0000000000000..3e973454c392c --- /dev/null +++ b/plugins/inputs/win_services/req_analysis.md @@ -0,0 +1,113 @@ +# Win_services telegraf input plugin analysis +This is analysis of a telegraf feature requirement demanding plugin for collecting windows services info, +originally requested in [telegraf issue #2714](https://github.com/influxdata/telegraf/issues/2714) + +## Feature +### Use Cases +- Admin needs to monitor state of selected windows services on the host, along with a few additional properties (display name, start-up mode) +- Admin wants to query info of monitored services +- Admin wants to query what services have defined properties + +### Feature requirements + * telegraf input plugin + * store service name, display name, state and startup mode + * configure what services should be monitored + +### Platform requirements + * WindowsXP and higher + * Windows Server 2003 and higher + + Defined by Windows Service API, used for querying services, availability + +### Admin rights +Admin privileges are required to read service info. However, as telegraf mostly run as a service running under local system account, it should be no problem. + +### Deployment +Feature request mentions monitoring of 5000 servers. This either means: +* deploying telegraf on each monitored host, what is the preferred option, as the other plugins can be used to monitor other stuff on the host, +* plugin has to monitor multiple servers, what would lead to more complex plugin (service input plugin) along with complex configuration + +## Implementation +### Storing service info +#### Measurement +There are two options to define what measurement would be +1. Store all service info in single measurement, e.g. win_services, configurable +2. Store services info per service + +Option 1. has the biggest benefit that user can easily query info about all services, e.g. all services in stopped state, but this leads to a lot of data in single measurement. As services measurement has the same schema ti make sense to use this. + +Option 2. diversifies the data but makes it difficult to query multiple services + +Q: What is best practise? is would prefer option 1.. Also, it could be configurable. + +#### Properties + +Basic properties to monitor and store, as initially requested, are: + * service name + * display name + * startup mode + * actual state + + They are also additional properties available(see [SERVICE_STATUS_PROCESS](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685992(v=vs.85).aspx) and [QUERY_SERVICE_CONFIG](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684950(v=vs.85).aspx) ), but those are quite advanced and not so suitable for monitoring. + + Display name and startup mode, are rather static properties and they will change rarely, but as most probable output is influxdb it will handle this with compaction. + +According to use cases, condition in queries can be based on _service name_ or a property (state, startup mode). +Services will won't be most probably filtered according to display name. + +So mapping to tag or field would be: + +* **Field** = displayName +* **Tag** = service name, startup mode, actual state + + Let's use following key name and 'types': + + Property|Key | Type + ---- |----- | --- + service name| service | string + display name| displayName | string + startup mode| startupMode | number + actual state| state | number + +Keys _startupMode_ and _state_ could be also string, a human readable representation of the attribute, but number are preferred by convention, as they can be mapped to string in visualization tools + +Mapping to text will be described in the plugin readme. + + ### Configuration + * User must be able to set what services to be monitored: + ```` + # Case-insensitive name of services to monitor. Empty of all services + Services = [ + "LanmanServer", + "TermService" + ] + ```` +Services in example should be available on all Windows edition and versions. + + * Configure measurement name + ```` + # Custom measurement. Default is win_services + Measurement = "MyServerServices" + ```` + As discussed in the [measurement]((#measurement)) paragraph we could have here configuration whether to store services info in one measurement. + + For the first version we can keep that hardcoded and based on feedback it could be changed. + + ### Storing Errors + There are basically two possible errors: + * Invalid service name given in configuration + * A service require special privileges + + This should be reported as a warning, not complete error of the Gather function + + Possible solutions: + 1. Report it once to log/console, in first run of Gather + 2. Store it once as a measurement, in first run of Gather + use _error_ tag for error message and _state_ tag with a special value (e.g. -1) to denote an error + 3. Store it repeatedly, in each measurement cycle, to retain this info. + + Q: What is the best practise? I would go with 3. + + ### Caching + Most service info is almost static and it could be cached. But as all the info about requested services Windows Service Manager stores in memory, even full listing takes just 8ms on Windows 10 on Core i5 (2 cores) laptop, + so caching seems overhead. \ No newline at end of file From 5f93ba176692439c60d8acf9ae467c206cf15aa5 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 12 Jul 2017 17:41:03 +0200 Subject: [PATCH 07/44] Fixed typo, better formation to highligh questions --- plugins/inputs/win_services/req_analysis.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/inputs/win_services/req_analysis.md b/plugins/inputs/win_services/req_analysis.md index 3e973454c392c..6ae2fdd495127 100644 --- a/plugins/inputs/win_services/req_analysis.md +++ b/plugins/inputs/win_services/req_analysis.md @@ -38,7 +38,7 @@ Option 1. has the biggest benefit that user can easily query info about all serv Option 2. diversifies the data but makes it difficult to query multiple services -Q: What is best practise? is would prefer option 1.. Also, it could be configurable. +_Q: What is the best practise? is would prefer option 1.. Also, it could be configurable._ #### Properties @@ -106,7 +106,7 @@ Services in example should be available on all Windows edition and versions. use _error_ tag for error message and _state_ tag with a special value (e.g. -1) to denote an error 3. Store it repeatedly, in each measurement cycle, to retain this info. - Q: What is the best practise? I would go with 3. + _Q: What is the best practise? I would go with 3._ ### Caching Most service info is almost static and it could be cached. But as all the info about requested services Windows Service Manager stores in memory, even full listing takes just 8ms on Windows 10 on Core i5 (2 cores) laptop, From f895ef3101d386a42521261c20a53f28b5f02707 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Thu, 13 Jul 2017 11:34:09 +0200 Subject: [PATCH 08/44] Adjusted based on feedback from Daniel Nelson --- plugins/inputs/win_services/req_analysis.md | 23 ++++++--------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/plugins/inputs/win_services/req_analysis.md b/plugins/inputs/win_services/req_analysis.md index 6ae2fdd495127..9b06a461e2f20 100644 --- a/plugins/inputs/win_services/req_analysis.md +++ b/plugins/inputs/win_services/req_analysis.md @@ -64,35 +64,24 @@ So mapping to tag or field would be: Property|Key | Type ---- |----- | --- - service name| service | string - display name| displayName | string - startup mode| startupMode | number - actual state| state | number + service name| service_name | string + display name| display_name | string + startup mode| startup_mode | string + actual state| state | string -Keys _startupMode_ and _state_ could be also string, a human readable representation of the attribute, but number are preferred by convention, as they can be mapped to string in visualization tools - -Mapping to text will be described in the plugin readme. +Keys _startupMode_ and _state_ will be a human readable representation of the attribute. ### Configuration * User must be able to set what services to be monitored: ```` # Case-insensitive name of services to monitor. Empty of all services - Services = [ + service_names = [ "LanmanServer", "TermService" ] ```` Services in example should be available on all Windows edition and versions. - * Configure measurement name - ```` - # Custom measurement. Default is win_services - Measurement = "MyServerServices" - ```` - As discussed in the [measurement]((#measurement)) paragraph we could have here configuration whether to store services info in one measurement. - - For the first version we can keep that hardcoded and based on feedback it could be changed. - ### Storing Errors There are basically two possible errors: * Invalid service name given in configuration From 570f6136abdd11bd3b3fc3153d2d7809574e95c3 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Thu, 13 Jul 2017 11:49:11 +0200 Subject: [PATCH 09/44] Adjusted based on feedback from Daniel Nelson --- plugins/inputs/win_services/req_analysis.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/plugins/inputs/win_services/req_analysis.md b/plugins/inputs/win_services/req_analysis.md index 9b06a461e2f20..b6e5f4dba44aa 100644 --- a/plugins/inputs/win_services/req_analysis.md +++ b/plugins/inputs/win_services/req_analysis.md @@ -88,14 +88,8 @@ Services in example should be available on all Windows edition and versions. * A service require special privileges This should be reported as a warning, not complete error of the Gather function - - Possible solutions: - 1. Report it once to log/console, in first run of Gather - 2. Store it once as a measurement, in first run of Gather - use _error_ tag for error message and _state_ tag with a special value (e.g. -1) to denote an error - 3. Store it repeatedly, in each measurement cycle, to retain this info. - - _Q: What is the best practise? I would go with 3._ + + As stated by Daniel, best practice is to use Accumulator.AddError and log it every time. Telegraf should handle multiple same errors. ### Caching Most service info is almost static and it could be cached. But as all the info about requested services Windows Service Manager stores in memory, even full listing takes just 8ms on Windows 10 on Core i5 (2 cores) laptop, From 45fb7431d8f42f079ddf59bd8b435232a82604b9 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Thu, 13 Jul 2017 14:09:45 +0200 Subject: [PATCH 10/44] Adjusted based na analysis feedback: - snake case names of fields/tag - simplified configuration - state and startup mode as string - better error reporting --- plugins/inputs/win_services/win_services.go | 79 +++++++++++---------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index 1abd90799c672..214e32d949794 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -6,33 +6,21 @@ import ( "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" "golang.org/x/sys/windows/svc/mgr" - "strconv" - "errors" + "fmt" ) var sampleConfig = ` - ## This plugin returns by default service state and startup mode - ## See the README file for more examples. - ## Uncomment examples below or write your own as you see fit. If the system - ## being polled for data does not have the Object at startup of the Telegraf - ## agent, it will not be gathered. - ## Settings: - - # Names of services to monitor. Empty for all - Services = [ - "Server" + ## Name of services to monitor. Set empty to monitor all the available services on the host + service_names = [ + "LanmanServer", + "TermService", ] - # CustomTagName=Group - # CustomTagValue=alpha ` -var description = "Input plugin to report Windows services info: name, display name, state, startup mode" +var description = "Input plugin to report Windows services info: service name, display name, state, startup mode" type Win_Services struct { - Services []string - Measurement string - CustomTagName string - CustomTagValue string + ServiceNames []string `toml:"service_names"` } type ServiceInfo struct { @@ -43,6 +31,24 @@ type ServiceInfo struct { Error error } +var ServiceStatesMap = map [int]string{ + 0x00000001: "service_stopped", + 0x00000002: "service_start_pending", + 0x00000003: "service_stop_pending", + 0x00000004: "service_running", + 0x00000005: "service_continue_pending", + 0x00000006: "service_pause_pending", + 0x00000007: "service_paused", +} + +var ServiceStartupModeMap = map [int]string{ + 0x00000000: "service_boot_start", + 0x00000001: "service_system_start", + 0x00000002: "service_auto_start", + 0x00000003: "service_demand_start", + 0x00000004: "service_disabled", + +} func (m *Win_Services) Description() string { return description @@ -55,29 +61,26 @@ func (m *Win_Services) SampleConfig() string { func (m *Win_Services) Gather(acc telegraf.Accumulator) error { - serviceInfos, err := listServices(m.Services) + serviceInfos, err := listServices(m.ServiceNames) if err != nil { return err } - if m.Measurement == "" { - m.Measurement = "win_services" - } - for _, service := range serviceInfos { - fields := make(map[string]interface{}) - tags := make(map[string]string) - tags["service"] = service.ServiceName if service.Error == nil { - fields["displayName"] = service.DisplayName - tags["state"] = strconv.Itoa(service.State) - tags["startupMode"] = strconv.Itoa(service.StartUpMode) + fields := make(map[string]interface{}) + tags := make(map[string]string) + + fields["display_name"] = service.DisplayName + tags["service_name"] = service.ServiceName + tags["state"] = ServiceStatesMap[service.State] + tags["startup_mode"] = ServiceStartupModeMap[service.StartUpMode] + + acc.AddFields("win_services", fields, tags) } else { - fields["error"] = service.Error.Error() - tags["state"] = strconv.Itoa(-1) //indicate error state + acc.AddError(err) } - acc.AddFields(m.Measurement, fields, tags) } return nil @@ -87,7 +90,7 @@ func listServices(userServices []string) ([]ServiceInfo, error) { scmgr, err := mgr.Connect() if err != nil { - return nil, errors.New("Could not open service manager: " + err.Error()); + return nil, fmt.Errorf("Could not open service manager: %s", err) } defer scmgr.Disconnect() @@ -96,7 +99,7 @@ func listServices(userServices []string) ([]ServiceInfo, error) { //Listing service names from system serviceNames, err = scmgr.ListServices() if err != nil { - return nil, errors.New("Could not list services: " + err.Error()); + return nil, fmt.Errorf("Could not list services: %s", err) } } else { serviceNames = userServices @@ -107,14 +110,14 @@ func listServices(userServices []string) ([]ServiceInfo, error) { serviceInfos[i].ServiceName = srvName srv, err := scmgr.OpenService(srvName) if err != nil { - serviceInfos[i].Error = errors.New("Could not open service: " + err.Error()); + serviceInfos[i].Error = fmt.Errorf("Could not open service '%s': %s",srvName, err) continue } srvStatus, err := srv.Query() if err == nil { serviceInfos[i].State = int(srvStatus.State) } else { - serviceInfos[i].Error = errors.New("Could not query service: " + err.Error()); + serviceInfos[i].Error = fmt.Errorf("Could not query service '%s': %s",srvName, err) } srvCfg, err := srv.Config() @@ -122,7 +125,7 @@ func listServices(userServices []string) ([]ServiceInfo, error) { serviceInfos[i].DisplayName = srvCfg.DisplayName serviceInfos[i].StartUpMode = int(srvCfg.StartType) } else { - serviceInfos[i].Error = errors.New("Could not get service config: " + err.Error()); + serviceInfos[i].Error = fmt.Errorf("Could not get config of service '%s': %s", srvName, err) } srv.Close() } From 96af3b7bb71903fa0214695e66f3d89f397eb6b1 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Thu, 13 Jul 2017 14:21:01 +0200 Subject: [PATCH 11/44] Fix: Fixed error reporting --- plugins/inputs/win_services/win_services.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index 214e32d949794..6d32c495dab5e 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -79,7 +79,7 @@ func (m *Win_Services) Gather(acc telegraf.Accumulator) error { acc.AddFields("win_services", fields, tags) } else { - acc.AddError(err) + acc.AddError(service.Error) } } From 37c2952265f6d58f640798324491d19b41fca28e Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Thu, 13 Jul 2017 14:36:06 +0200 Subject: [PATCH 12/44] - proper readme for win_services plugin --- plugins/inputs/win_services/README.md | 362 +++----------------------- 1 file changed, 41 insertions(+), 321 deletions(-) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index a19f4c8b5ab38..d6fe5ba9eb83a 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -1,336 +1,56 @@ -# win_services readme +# Telegraf Plugin: win_services +Input plugin to report Windows services info: service name, display name, state, startup mode -The way this plugin works is that on load of Telegraf, -the plugin will be handed configuration from Telegraf. -This configuration is parsed and then tested for validity such as -if the Object, Instance and Counter existing. -If it does not match at startup, it will not be fetched. -Exceptions to this are in cases where you query for all instances "*". -By default the plugin does not return _Total -when it is querying for all (*) as this is redundant. +### Configuration: -## Basics - -The examples contained in this file have been found on the internet -as counters used when performance monitoring - Active Directory and IIS in perticular. - There are a lot other good objects to monitor, if you know what to look for. - This file is likely to be updated in the future with more examples for - useful configurations for separate scenarios. - -### Plugin wide - -Plugin wide entries are underneath `[[inputs.win_perf_counters]]`. - -#### PrintValid - -Bool, if set to `true` will print out all matching performance objects. - -Example: -`PrintValid=true` - -#### PreVistaSupport - -Bool, if set to `true` will use the localized PerfCounter interface that is present before Vista for backwards compatability. - -It is recommended NOT to use this on OSes starting with Vista and newer because it requires more configuration to use this than the newer interface present since Vista. - -Example for Windows Server 2003, this would be set to true: -`PreVistaSupport=true` - -### Object - -See Entry below. - -### Entry -A new configuration entry consists of the TOML header to start with, -`[[inputs.win_perf_counters.object]]`. -This must follow before other plugins configuration, -beneath the main win_perf_counters entry, `[[inputs.win_perf_counters]]`. - -Following this is 3 required key/value pairs and the three optional parameters and their usage. - -#### ObjectName -**Required** - -ObjectName is the Object to query for, like Processor, DirectoryServices, LogicalDisk or similar. - -Example: `ObjectName = "LogicalDisk"` - -#### Instances -**Required** - -Instances (this is an array) is the instances of a counter you would like returned, -it can be one or more values. - -Example, `Instances = ["C:","D:","E:"]` will return only for the instances -C:, D: and E: where relevant. To get all instances of a Counter, use ["*"] only. -By default any results containing _Total are stripped, -unless this is specified as the wanted instance. -Alternatively see the option IncludeTotal below. - -Some Objects does not have instances to select from at all, -here only one option is valid if you want data back, -and that is to specify `Instances = ["------"]`. - -#### Counters -**Required** - -Counters (this is an array) is the counters of the ObjectName -you would like returned, it can also be one or more values. - -Example: `Counters = ["% Idle Time", "% Disk Read Time", "% Disk Write Time"]` -This must be specified for every counter you want the results of, -it is not possible to ask for all counters in the ObjectName. - -#### Measurement -*Optional* - -This key is optional, if it is not set it will be win_perf_counters. -In InfluxDB this is the key by which the returned data is stored underneath, -so for ordering your data in a good manner, -this is a good key to set with where you want your IIS and Disk results stored, -separate from Processor results. - -Example: `Measurement = "win_disk" - -#### IncludeTotal -*Optional* - -This key is optional, it is a simple bool. -If it is not set to true or included it is treated as false. -This key only has an effect if Instances is set to "*" -and you would also like all instances containg _Total returned, -like "_Total", "0,_Total" and so on where applicable -(Processor Information is one example). - -#### WarnOnMissing -*Optional* - -This key is optional, it is a simple bool. -If it is not set to true or included it is treated as false. -This only has an effect on the first execution of the plugin, -it will print out any ObjectName/Instance/Counter combinations -asked for that does not match. Useful when debugging new configurations. - -#### FailOnMissing -*Internal* - -This key should not be used, it is for testing purposes only. -It is a simple bool, if it is not set to true or included this is treaded as false. -If this is set to true, the plugin will abort and end prematurely -if any of the combinations of ObjectName/Instances/Counters are invalid. - -## Examples - -### Generic Queries -``` - - [[inputs.win_perf_counters.object]] - # Processor usage, alternative to native, reports on a per core. - ObjectName = "Processor" - Instances = ["*"] - Counters = ["% Idle Time", "% Interrupt Time", "% Privileged Time", "% User Time", "% Processor Time"] - Measurement = "win_cpu" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - # Disk times and queues - ObjectName = "LogicalDisk" - Instances = ["*"] - Counters = ["% Idle Time", "% Disk Time","% Disk Read Time", "% Disk Write Time", "% User Time", "Current Disk Queue Length"] - Measurement = "win_disk" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - ObjectName = "System" - Counters = ["Context Switches/sec","System Calls/sec", "Processor Queue Length"] - Instances = ["------"] - Measurement = "win_system" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - # Example query where the Instance portion must be removed to get data back, such as from the Memory object. - ObjectName = "Memory" - Counters = ["Available Bytes","Cache Faults/sec","Demand Zero Faults/sec","Page Faults/sec","Pages/sec","Transition Faults/sec","Pool Nonpaged Bytes","Pool Paged Bytes"] - Instances = ["------"] # Use 6 x - to remove the Instance bit from the query. - Measurement = "win_mem" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - # more counters for the Network Interface Object can be found at - # https://msdn.microsoft.com/en-us/library/ms803962.aspx - ObjectName = "Network Interface" - Counters = ["Bytes Received/sec","Bytes Sent/sec","Packets Received/sec","Packets Sent/sec"] - Instances = ["*"] # Use 6 x - to remove the Instance bit from the query. - Measurement = "win_net" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). -``` - -### Active Directory Domain Controller -``` - [[inputs.win_perf_counters.object]] - ObjectName = "DirectoryServices" - Instances = ["*"] - Counters = ["Base Searches/sec","Database adds/sec","Database deletes/sec","Database modifys/sec","Database recycles/sec","LDAP Client Sessions","LDAP Searches/sec","LDAP Writes/sec"] - Measurement = "win_ad" # Set an alternative measurement to win_perf_counters if wanted. - #Instances = [""] # Gathers all instances by default, specify to only gather these - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - ObjectName = "Security System-Wide Statistics" - Instances = ["*"] - Counters = ["NTLM Authentications","Kerberos Authentications","Digest Authentications"] - Measurement = "win_ad" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - ObjectName = "Database" - Instances = ["*"] - Counters = ["Database Cache % Hit","Database Cache Page Fault Stalls/sec","Database Cache Page Faults/sec","Database Cache Size"] - Measurement = "win_db" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). -``` - -### DFS Namespace + Domain Controllers -``` - [[inputs.win_perf_counters.object]] - # AD, DFS N, Useful if the server hosts a DFS Namespace or is a Domain Controller - ObjectName = "DFS Namespace Service Referrals" - Instances = ["*"] - Counters = ["Requests Processed","Requests Failed","Avg. Response Time"] - Measurement = "win_dfsn" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - #WarnOnMissing = false # Print out when the performance counter is missing, either of object, counter or instance. +```toml +[[inputs.win_services]] + ## Name of services to monitor. Set empty to monitor all the available services on the host + service_names = [ + "LanmanServer", + "TermService", + ] ``` +### Measurements & Fields: -### DFS Replication + Domain Controllers -``` - [[inputs.win_perf_counters.object]] - # AD, DFS R, Useful if the server hosts a DFS Replication folder or is a Domain Controller - ObjectName = "DFS Replication Service Volumes" - Instances = ["*"] - Counters = ["Data Lookups","Database Commits"] - Measurement = "win_dfsr" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - #WarnOnMissing = false # Print out when the performance counter is missing, either of object, counter or instance. -``` - - -### DNS Server + Domain Controllers -``` - [[inputs.win_perf_counters.object]] - ObjectName = "DNS" - Counters = ["Dynamic Update Received","Dynamic Update Rejected","Recursive Queries","Recursive Queries Failure","Secure Update Failure","Secure Update Received","TCP Query Received","TCP Response Sent","UDP Query Received","UDP Response Sent","Total Query Received","Total Response Sent"] - Instances = ["------"] - Measurement = "win_dns" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). -``` - -### IIS / ASP.NET -``` - [[inputs.win_perf_counters.object]] - # HTTP Service request queues in the Kernel before being handed over to User Mode. - ObjectName = "HTTP Service Request Queues" - Instances = ["*"] - Counters = ["CurrentQueueSize","RejectedRequests"] - Measurement = "win_http_queues" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +- win_services + - display_name - [[inputs.win_perf_counters.object]] - # IIS, ASP.NET Applications - ObjectName = "ASP.NET Applications" - Counters = ["Cache Total Entries","Cache Total Hit Ratio","Cache Total Turnover Rate","Output Cache Entries","Output Cache Hits","Output Cache Hit Ratio","Output Cache Turnover Rate","Compilations Total","Errors Total/Sec","Pipeline Instance Count","Requests Executing","Requests in Application Queue","Requests/Sec"] - Instances = ["*"] - Measurement = "win_aspnet_app" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +### Tags: - [[inputs.win_perf_counters.object]] - # IIS, ASP.NET - ObjectName = "ASP.NET" - Counters = ["Application Restarts","Request Wait Time","Requests Current","Requests Queued","Requests Rejected"] - Instances = ["*"] - Measurement = "win_aspnet" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +- All measurements have the following tags: + - service_name + - state + - startup_mode - [[inputs.win_perf_counters.object]] - # IIS, Web Service - ObjectName = "Web Service" - Counters = ["Get Requests/sec","Post Requests/sec","Connection Attempts/sec","Current Connections","ISAPI Extension Requests/sec"] - Instances = ["*"] - Measurement = "win_websvc" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +The `state` tag can have following values: +* _service_stopped_ +* _service_start_pending_ +* _service_stop_pending_ +* _service_running_ +* _service_continue_pending_ +* _service_pause_pending_ +* _service_paused_ + +The `startup_mode` tag can have following values: +* _service_boot_start_ +* _service_system_start_ +* _service_auto_start_ +* _service_demand_start_ +* _service_disabled_ - [[inputs.win_perf_counters.object]] - # Web Service Cache / IIS - ObjectName = "Web Service Cache" - Counters = ["URI Cache Hits %","Kernel: URI Cache Hits %","File Cache Hits %"] - Instances = ["*"] - Measurement = "win_websvc_cache" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). -``` +### Example Output: +Using default configuration: -### Process +When run with: ``` - [[inputs.win_perf_counters.object]] - # Process metrics, in this case for IIS only - ObjectName = "Process" - Counters = ["% Processor Time","Handle Count","Private Bytes","Thread Count","Virtual Bytes","Working Set"] - Instances = ["w3wp"] - Measurement = "win_proc" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +E:\Telegraf>telegraf.exe -config telegraf.conf -test ``` - - -### .NET Montioring +It produces: ``` - [[inputs.win_perf_counters.object]] - # .NET CLR Exceptions, in this case for IIS only - ObjectName = ".NET CLR Exceptions" - Counters = ["# of Exceps Thrown / sec"] - Instances = ["w3wp"] - Measurement = "win_dotnet_exceptions" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - # .NET CLR Jit, in this case for IIS only - ObjectName = ".NET CLR Jit" - Counters = ["% Time in Jit","IL Bytes Jitted / sec"] - Instances = ["w3wp"] - Measurement = "win_dotnet_jit" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - # .NET CLR Loading, in this case for IIS only - ObjectName = ".NET CLR Loading" - Counters = ["% Time Loading"] - Instances = ["w3wp"] - Measurement = "win_dotnet_loading" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - # .NET CLR LocksAndThreads, in this case for IIS only - ObjectName = ".NET CLR LocksAndThreads" - Counters = ["# of current logical Threads","# of current physical Threads","# of current recognized threads","# of total recognized threads","Queue Length / sec","Total # of Contentions","Current Queue Length"] - Instances = ["w3wp"] - Measurement = "win_dotnet_locks" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - # .NET CLR Memory, in this case for IIS only - ObjectName = ".NET CLR Memory" - Counters = ["% Time in GC","# Bytes in all Heaps","# Gen 0 Collections","# Gen 1 Collections","# Gen 2 Collections","# Induced GC","Allocated Bytes/sec","Finalization Survivors","Gen 0 heap size","Gen 1 heap size","Gen 2 heap size","Large Object Heap size","# of Pinned Objects"] - Instances = ["w3wp"] - Measurement = "win_dotnet_mem" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). - - [[inputs.win_perf_counters.object]] - # .NET CLR Security, in this case for IIS only - ObjectName = ".NET CLR Security" - Counters = ["% Time in RT checks","Stack Walk Depth","Total Runtime Checks"] - Instances = ["w3wp"] - Measurement = "win_dotnet_security" - #IncludeTotal=false #Set to true to include _Total instance when querying for all (*). +* Plugin: inputs.win_services, Collection 1 +> win_services,state=service_running,startup_mode=service_auto_start,host=WIN2008R2H401,service_name=LanmanServer display_name="Server" 1499947615000000000 +> win_services,service_name=TermService,state=service_stopped,startup_mode=service_demand_start,host=WIN2008R2H401 display_name="Remote Desktop Services" 1499947615000000000 ``` From 03447bc9b543b605908b66161fd75da5068d37b6 Mon Sep 17 00:00:00 2001 From: karel-rehor Date: Thu, 13 Jul 2017 15:43:03 +0200 Subject: [PATCH 13/44] proof reading --- plugins/inputs/win_services/README.md | 10 +-- plugins/inputs/win_services/req_analysis.md | 72 ++++++++++----------- plugins/inputs/win_services/win_services.go | 2 +- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index d6fe5ba9eb83a..5f618d48a5944 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -5,7 +5,7 @@ Input plugin to report Windows services info: service name, display name, state, ```toml [[inputs.win_services]] - ## Name of services to monitor. Set empty to monitor all the available services on the host + ## Names of the services to monitor. Leave empty to monitor all the available services on the host service_names = [ "LanmanServer", "TermService", @@ -24,7 +24,7 @@ Input plugin to report Windows services info: service name, display name, state, - state - startup_mode -The `state` tag can have following values: +The `state` tag can have the following values: * _service_stopped_ * _service_start_pending_ * _service_stop_pending_ @@ -32,8 +32,8 @@ The `state` tag can have following values: * _service_continue_pending_ * _service_pause_pending_ * _service_paused_ - -The `startup_mode` tag can have following values: + +The `startup_mode` tag can have the following values: * _service_boot_start_ * _service_system_start_ * _service_auto_start_ @@ -42,7 +42,7 @@ The `startup_mode` tag can have following values: ### Example Output: -Using default configuration: +Using the default configuration: When run with: ``` diff --git a/plugins/inputs/win_services/req_analysis.md b/plugins/inputs/win_services/req_analysis.md index b6e5f4dba44aa..0f97613a69041 100644 --- a/plugins/inputs/win_services/req_analysis.md +++ b/plugins/inputs/win_services/req_analysis.md @@ -1,10 +1,10 @@ # Win_services telegraf input plugin analysis -This is analysis of a telegraf feature requirement demanding plugin for collecting windows services info, +This is an analysis of a telegraf feature requirement demanding a plugin for collecting windows services info, originally requested in [telegraf issue #2714](https://github.com/influxdata/telegraf/issues/2714) ## Feature ### Use Cases -- Admin needs to monitor state of selected windows services on the host, along with a few additional properties (display name, start-up mode) +- Admin needs to monitor the states of selected windows services on the host, along with a few additional properties (display name, start-up mode) - Admin wants to query info of monitored services - Admin wants to query what services have defined properties @@ -12,56 +12,56 @@ originally requested in [telegraf issue #2714](https://github.com/influxdata/tel * telegraf input plugin * store service name, display name, state and startup mode * configure what services should be monitored - + ### Platform requirements - * WindowsXP and higher + * WindowsXP and higher * Windows Server 2003 and higher - + Defined by Windows Service API, used for querying services, availability ### Admin rights -Admin privileges are required to read service info. However, as telegraf mostly run as a service running under local system account, it should be no problem. +Admin privileges are required to read service info. However, as telegraf mostly runs as a service under a local system account, it should be no problem. ### Deployment -Feature request mentions monitoring of 5000 servers. This either means: -* deploying telegraf on each monitored host, what is the preferred option, as the other plugins can be used to monitor other stuff on the host, -* plugin has to monitor multiple servers, what would lead to more complex plugin (service input plugin) along with complex configuration +Feature request mentions the monitoring of 5000 servers. This either means: +* deploying telegraf on each monitored host, which is the preferred option, as the other plugins can be used to monitor other stuff on the host, +* plugin has to monitor multiple servers, which would lead to a more complex plugin (service input plugin) along with complex configuration ## Implementation ### Storing service info #### Measurement -There are two options to define what measurement would be +There are two options to define what a measurement might be 1. Store all service info in single measurement, e.g. win_services, configurable -2. Store services info per service +2. Store service info per service -Option 1. has the biggest benefit that user can easily query info about all services, e.g. all services in stopped state, but this leads to a lot of data in single measurement. As services measurement has the same schema ti make sense to use this. +Option 1. has the biggest benefit in that a user can easily query info about all services, e.g. all services in a stopped state, but this leads to a lot of data in a single measurement, as service measurements have to have the same schema for it to make sense to use this. Option 2. diversifies the data but makes it difficult to query multiple services -_Q: What is the best practise? is would prefer option 1.. Also, it could be configurable._ +_Q: What is the best practise? I would prefer option 1.. Also, it could be configurable._ #### Properties Basic properties to monitor and store, as initially requested, are: * service name * display name - * startup mode + * startup mode * actual state - - They are also additional properties available(see [SERVICE_STATUS_PROCESS](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685992(v=vs.85).aspx) and [QUERY_SERVICE_CONFIG](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684950(v=vs.85).aspx) ), but those are quite advanced and not so suitable for monitoring. - - Display name and startup mode, are rather static properties and they will change rarely, but as most probable output is influxdb it will handle this with compaction. - -According to use cases, condition in queries can be based on _service name_ or a property (state, startup mode). -Services will won't be most probably filtered according to display name. -So mapping to tag or field would be: + There are also additional properties available(see [SERVICE_STATUS_PROCESS](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685992(v=vs.85).aspx) and [QUERY_SERVICE_CONFIG](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684950(v=vs.85).aspx) ), but these are quite advanced and not so suitable for monitoring. + + Display name and startup mode are rather static properties and they will change rarely, but as most probable output is influxdb it will handle this with compaction. + +According to the use cases, conditions in queries can be based on _service name_ or a property (state, startup mode). +Services most probably won't be filtered according to display name. + +So the mapping to a tag or a field would be: * **Field** = displayName * **Tag** = service name, startup mode, actual state - Let's use following key name and 'types': - + Let's use the following key name and 'types': + Property|Key | Type ---- |----- | --- service name| service_name | string @@ -69,10 +69,10 @@ So mapping to tag or field would be: startup mode| startup_mode | string actual state| state | string -Keys _startupMode_ and _state_ will be a human readable representation of the attribute. +The keys _startupMode_ and _state_ will be a human readable representation of the attribute. ### Configuration - * User must be able to set what services to be monitored: + * User must be able to set what services will be monitored: ```` # Case-insensitive name of services to monitor. Empty of all services service_names = [ @@ -80,17 +80,17 @@ Keys _startupMode_ and _state_ will be a human readable representation of the a "TermService" ] ```` -Services in example should be available on all Windows edition and versions. - +Services in examples should be available on all Windows editions and versions. + ### Storing Errors There are basically two possible errors: * Invalid service name given in configuration - * A service require special privileges - - This should be reported as a warning, not complete error of the Gather function - - As stated by Daniel, best practice is to use Accumulator.AddError and log it every time. Telegraf should handle multiple same errors. - + * A service requires special privileges + + This should be reported as a warning, not as a complete error of the Gather function + + As stated by Daniel, best practice is to use Accumulator.AddError and log it every time. Telegraf should handle multiple instances of the same errors. + ### Caching - Most service info is almost static and it could be cached. But as all the info about requested services Windows Service Manager stores in memory, even full listing takes just 8ms on Windows 10 on Core i5 (2 cores) laptop, - so caching seems overhead. \ No newline at end of file + Most service info is almost static and it could be cached. But as all the info about requested services that Windows Service Manager stores in memory, even a full listing, takes just 8ms on Windows 10 on a Core i5 (2 cores) laptop, + so caching seems like overhead. diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index 6d32c495dab5e..f41a66baa4cf3 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -10,7 +10,7 @@ import ( ) var sampleConfig = ` - ## Name of services to monitor. Set empty to monitor all the available services on the host + ## Names of the services to monitor. Leave empty to monitor all the available services on the host service_names = [ "LanmanServer", "TermService", From b05aa4a26069291e1de473a473f6ae1086ae6b12 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Thu, 13 Jul 2017 15:57:55 +0200 Subject: [PATCH 14/44] small english correction --- plugins/inputs/win_services/req_analysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/win_services/req_analysis.md b/plugins/inputs/win_services/req_analysis.md index 0f97613a69041..ff7d2f2081162 100644 --- a/plugins/inputs/win_services/req_analysis.md +++ b/plugins/inputs/win_services/req_analysis.md @@ -20,7 +20,7 @@ originally requested in [telegraf issue #2714](https://github.com/influxdata/tel Defined by Windows Service API, used for querying services, availability ### Admin rights -Admin privileges are required to read service info. However, as telegraf mostly runs as a service under a local system account, it should be no problem. +Admin privileges are required to read service info. However, as telegraf mostly runs as a service under the Local System account, it should be no problem. ### Deployment Feature request mentions the monitoring of 5000 servers. This either means: From 2bd9a783c9eb010d43d8a348e0fff304ee718fc0 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Fri, 14 Jul 2017 16:08:44 +0200 Subject: [PATCH 15/44] using state and startup_mode as fields and display_name as a tag --- plugins/inputs/win_services/README.md | 20 ++++++++++---------- plugins/inputs/win_services/win_services.go | 7 ++++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index 5f618d48a5944..8c33c53d71284 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -15,15 +15,9 @@ Input plugin to report Windows services info: service name, display name, state, ### Measurements & Fields: - win_services - - display_name - -### Tags: - -- All measurements have the following tags: - - service_name - state - startup_mode - + The `state` tag can have the following values: * _service_stopped_ * _service_start_pending_ @@ -38,7 +32,13 @@ The `startup_mode` tag can have the following values: * _service_system_start_ * _service_auto_start_ * _service_demand_start_ -* _service_disabled_ +* _service_disabled_ + +### Tags: + +- All measurements have the following tags: + - service_name + - display_name ### Example Output: @@ -51,6 +51,6 @@ E:\Telegraf>telegraf.exe -config telegraf.conf -test It produces: ``` * Plugin: inputs.win_services, Collection 1 -> win_services,state=service_running,startup_mode=service_auto_start,host=WIN2008R2H401,service_name=LanmanServer display_name="Server" 1499947615000000000 -> win_services,service_name=TermService,state=service_stopped,startup_mode=service_demand_start,host=WIN2008R2H401 display_name="Remote Desktop Services" 1499947615000000000 +> win_services,host=WIN2008R2H401,display_name=Server,service_name=LanmanServer state="service_running",startup_mode="service_auto_start" 15 00040669000000000 +> win_services,display_name=Remote\ Desktop\ Services,service_name=TermService,host=WIN2008R2H401 state="service_stopped",startup_mode="service_demand_start" 1500040669000000000 ``` diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index f41a66baa4cf3..33903451b8519 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -72,10 +72,11 @@ func (m *Win_Services) Gather(acc telegraf.Accumulator) error { fields := make(map[string]interface{}) tags := make(map[string]string) - fields["display_name"] = service.DisplayName + tags["display_name"] = service.DisplayName tags["service_name"] = service.ServiceName - tags["state"] = ServiceStatesMap[service.State] - tags["startup_mode"] = ServiceStartupModeMap[service.StartUpMode] + + fields["state"] = ServiceStatesMap[service.State] + fields["startup_mode"] = ServiceStartupModeMap[service.StartUpMode] acc.AddFields("win_services", fields, tags) } else { From 3d4e3dbbed2675da8a14f6366fd1e55501c06480 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Sat, 15 Jul 2017 22:34:56 +0200 Subject: [PATCH 16/44] Added win_services unit tests --- .../inputs/win_services/win_services_test.go | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/plugins/inputs/win_services/win_services_test.go b/plugins/inputs/win_services/win_services_test.go index a26d210239377..df237c798dafd 100644 --- a/plugins/inputs/win_services/win_services_test.go +++ b/plugins/inputs/win_services/win_services_test.go @@ -2,5 +2,99 @@ package win_services +import ( + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" + "golang.org/x/sys/windows/svc/mgr" +) +var InvalidServices = []string{"XYZ1@", "ZYZ@", "SDF_@#"} +var KnownServices = []string{"LanmanServer", "TermService"} +func TestList(t *testing.T) { + services, err := listServices(KnownServices) + require.NoError(t, err) + assert.Len(t, services, 2, "Different number of services") + assert.Equal(t, services[0].ServiceName, KnownServices[0]) + assert.Nil(t, services[0].Error) + assert.Equal(t, services[1].ServiceName, KnownServices[1]) + assert.Nil(t, services[1].Error) +} + +func TestEmptyList(t *testing.T) { + services, err := listServices([]string {}) + require.NoError(t, err) + assert.Condition(t, func () bool { return len(services) > 20}, "Too few service") +} + +func TestListEr(t *testing.T) { + services, err := listServices(InvalidServices) + require.NoError(t, err) + assert.Len(t, services, 3, "Different number of services") + for i := 0; i < 3; i++ { + assert.Equal(t, services[i].ServiceName, InvalidServices[i]) + assert.NotNil(t, services[i].Error) + } +} + +func TestGather(t *testing.T) { + ws := &Win_Services{KnownServices} + assert.Len(t, ws.ServiceNames, 2, "Different number of services") + var acc testutil.Accumulator + require.NoError(t, ws.Gather(&acc)) + assert.Len(t, acc.Errors, 0, "There should be no errors after gather") + + for i := 0; i < 2; i++ { + fields := make(map[string]interface{}) + tags := make(map[string]string) + si := getServiceInfo(KnownServices[i]) + fields["state"] = ServiceStatesMap[si.State] + fields["startup_mode"] = ServiceStartupModeMap[si.StartUpMode] + tags["service_name"] = si.ServiceName + tags["display_name"] = si.DisplayName + acc.AssertContainsTaggedFields(t, "win_services", fields, tags) + } + +} + +func TestGatherErrors(t *testing.T) { + ws := &Win_Services{InvalidServices} + assert.Len(t, ws.ServiceNames, 3, "Different number of services") + var acc testutil.Accumulator + require.NoError(t, ws.Gather(&acc)) + assert.Len(t, acc.Errors, 3, "There should be 3 errors after gather") +} + +func getServiceInfo(srvName string) (*ServiceInfo) { + + scmgr, err := mgr.Connect() + if err != nil { + return nil + } + defer scmgr.Disconnect() + + srv, err := scmgr.OpenService(srvName) + if err != nil { + return nil + } + var si ServiceInfo + si.ServiceName = srvName + srvStatus, err := srv.Query() + if err == nil { + si.State = int(srvStatus.State) + } else { + si.Error = err + } + + srvCfg, err := srv.Config() + if err == nil { + si.DisplayName = srvCfg.DisplayName + si.StartUpMode = int(srvCfg.StartType) + } else { + si.Error = err + } + srv.Close() + return &si +} From b88e0e58fc4ed69a5612ca6c77791b35250bd506 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Sat, 15 Jul 2017 22:44:50 +0200 Subject: [PATCH 17/44] Added comment explaining condition for running --- plugins/inputs/win_services/win_services_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/inputs/win_services/win_services_test.go b/plugins/inputs/win_services/win_services_test.go index df237c798dafd..b80b6dbd3f71a 100644 --- a/plugins/inputs/win_services/win_services_test.go +++ b/plugins/inputs/win_services/win_services_test.go @@ -1,5 +1,6 @@ // +build windows +//this test must be run under administrator account package win_services import ( From 1ab4cdc79c282dfcb31091b7c6f04fd11e0f496d Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Sun, 16 Jul 2017 22:57:38 +0200 Subject: [PATCH 18/44] - Removing prefix 'service_' from state and startup mode enumerations - added sample TICK script --- plugins/inputs/win_services/README.md | 44 ++++++++++++++++++--------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index 8c33c53d71284..bfc1a388d641e 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -19,20 +19,20 @@ Input plugin to report Windows services info: service name, display name, state, - startup_mode The `state` tag can have the following values: -* _service_stopped_ -* _service_start_pending_ -* _service_stop_pending_ -* _service_running_ -* _service_continue_pending_ -* _service_pause_pending_ -* _service_paused_ +* _stopped_ +* _start_pending_ +* _stop_pending_ +* _running_ +* _continue_pending_ +* _pause_pending_ +* _paused_ The `startup_mode` tag can have the following values: -* _service_boot_start_ -* _service_system_start_ -* _service_auto_start_ -* _service_demand_start_ -* _service_disabled_ +* _boot_start_ +* _system_start_ +* _auto_start_ +* _demand_start_ +* _disabled_ ### Tags: @@ -51,6 +51,22 @@ E:\Telegraf>telegraf.exe -config telegraf.conf -test It produces: ``` * Plugin: inputs.win_services, Collection 1 -> win_services,host=WIN2008R2H401,display_name=Server,service_name=LanmanServer state="service_running",startup_mode="service_auto_start" 15 00040669000000000 -> win_services,display_name=Remote\ Desktop\ Services,service_name=TermService,host=WIN2008R2H401 state="service_stopped",startup_mode="service_demand_start" 1500040669000000000 +> win_services,host=WIN2008R2H401,display_name=Server,service_name=LanmanServer state="running",startup_mode="auto_start" 15 00040669000000000 +> win_services,display_name=Remote\ Desktop\ Services,service_name=TermService,host=WIN2008R2H401 state="stopped",startup_mode="demand_start" 1500040669000000000 ``` +### TICK Scripts + +A sample TICK script for notification about a not running service and about its changing back to the running state via HTTP post: + +``` +stream + |from() + .database('telegraf') + .retentionPolicy('autogen') + .measurement('win_services') + |alert() + .crit(lambda: "state" != 'running') + .stateChangesOnly() + .message('Service {{ index .Tags "service_name" }} on Host {{ index .Tags "host" }} {{ index .Fields "state" }} ') + .post('http://localhost:666/alert/cpu') +``` \ No newline at end of file From b5a5a1f349fb839ad25926a2b297d14d249f9395 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Sun, 16 Jul 2017 22:58:51 +0200 Subject: [PATCH 19/44] Edited to reflect actual state --- plugins/inputs/win_services/req_analysis.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/inputs/win_services/req_analysis.md b/plugins/inputs/win_services/req_analysis.md index ff7d2f2081162..0a68e96e0b04f 100644 --- a/plugins/inputs/win_services/req_analysis.md +++ b/plugins/inputs/win_services/req_analysis.md @@ -57,8 +57,8 @@ Services most probably won't be filtered according to display name. So the mapping to a tag or a field would be: -* **Field** = displayName -* **Tag** = service name, startup mode, actual state +* **Field** = actual state, startup mode, +* **Tag** = service name, displayName Let's use the following key name and 'types': From 957015cba0359f6829a520a1514e50071f11d4d9 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Sun, 16 Jul 2017 23:07:33 +0200 Subject: [PATCH 20/44] Removing prefix 'service_' from state and startup mode enumerations --- plugins/inputs/win_services/win_services.go | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index 33903451b8519..2f830fda09d20 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -32,21 +32,21 @@ type ServiceInfo struct { } var ServiceStatesMap = map [int]string{ - 0x00000001: "service_stopped", - 0x00000002: "service_start_pending", - 0x00000003: "service_stop_pending", - 0x00000004: "service_running", - 0x00000005: "service_continue_pending", - 0x00000006: "service_pause_pending", - 0x00000007: "service_paused", + 0x00000001: "stopped", + 0x00000002: "start_pending", + 0x00000003: "stop_pending", + 0x00000004: "running", + 0x00000005: "continue_pending", + 0x00000006: "pause_pending", + 0x00000007: "paused", } var ServiceStartupModeMap = map [int]string{ - 0x00000000: "service_boot_start", - 0x00000001: "service_system_start", - 0x00000002: "service_auto_start", - 0x00000003: "service_demand_start", - 0x00000004: "service_disabled", + 0x00000000: "boot_start", + 0x00000001: "system_start", + 0x00000002: "auto_start", + 0x00000003: "demand_start", + 0x00000004: "disabled", } From b54b8a0c0245e50c77179f6e971c8c4a3b4a4015 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Sun, 16 Jul 2017 23:15:22 +0200 Subject: [PATCH 21/44] Revert "Marked with TODO" This reverts commit 3b4df689da153cea66c6b8ce02f3a2b6bb5d941a. --- plugins/inputs/win_perf_counters/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/inputs/win_perf_counters/README.md b/plugins/inputs/win_perf_counters/README.md index 190026743f50d..06d9f1b9d5f23 100644 --- a/plugins/inputs/win_perf_counters/README.md +++ b/plugins/inputs/win_perf_counters/README.md @@ -1,7 +1,5 @@ # win_perf_counters readme -vh: TODO - The way this plugin works is that on load of Telegraf, the plugin will be handed configuration from Telegraf. This configuration is parsed and then tested for validity such as From 3b3e97384141253ba4f179b2412d511db0425019 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Mon, 17 Jul 2017 09:13:17 +0200 Subject: [PATCH 22/44] - Better script description - Better script alert message --- plugins/inputs/win_services/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index bfc1a388d641e..4ef52e5e9d34d 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -56,7 +56,9 @@ It produces: ``` ### TICK Scripts -A sample TICK script for notification about a not running service and about its changing back to the running state via HTTP post: +A sample TICK script for a notification about a not running service. +It notifies when any service changes its state to be not _running_ and when it changes state back to _running_. +The notification is sent via HTTP POST call. ``` stream @@ -67,6 +69,6 @@ stream |alert() .crit(lambda: "state" != 'running') .stateChangesOnly() - .message('Service {{ index .Tags "service_name" }} on Host {{ index .Tags "host" }} {{ index .Fields "state" }} ') + .message('Service {{ index .Tags "service_name" }} on Host {{ index .Tags "host" }} is {{ index .Fields "state" }} ') .post('http://localhost:666/alert/cpu') ``` \ No newline at end of file From c568b19430c5145bfbf5e0044dcc9d16f2cfc798 Mon Sep 17 00:00:00 2001 From: karel-rehor Date: Mon, 17 Jul 2017 10:37:28 +0200 Subject: [PATCH 23/44] proof read new section on TICK Scripts --- plugins/inputs/win_services/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index 4ef52e5e9d34d..fae4a7593c9f5 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -17,7 +17,7 @@ Input plugin to report Windows services info: service name, display name, state, - win_services - state - startup_mode - + The `state` tag can have the following values: * _stopped_ * _start_pending_ @@ -56,9 +56,9 @@ It produces: ``` ### TICK Scripts -A sample TICK script for a notification about a not running service. -It notifies when any service changes its state to be not _running_ and when it changes state back to _running_. -The notification is sent via HTTP POST call. +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. ``` stream @@ -71,4 +71,4 @@ stream .stateChangesOnly() .message('Service {{ index .Tags "service_name" }} on Host {{ index .Tags "host" }} is {{ index .Fields "state" }} ') .post('http://localhost:666/alert/cpu') -``` \ No newline at end of file +``` From e0a8773ab2df19dfa33696b0f9bc207f5b0cefba Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Mon, 17 Jul 2017 11:01:08 +0200 Subject: [PATCH 24/44] - Fixed TICK script --- plugins/inputs/win_services/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index fae4a7593c9f5..bc15b3dd05379 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -66,6 +66,7 @@ stream .database('telegraf') .retentionPolicy('autogen') .measurement('win_services') + .groupBy('host','service_name') |alert() .crit(lambda: "state" != 'running') .stateChangesOnly() From d203d20584f93e54562a2d1844f7147493aa39ea Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Mon, 17 Jul 2017 11:10:56 +0200 Subject: [PATCH 25/44] Delete obsolete file --- plugins/inputs/win_services/req_analysis.md | 96 --------------------- 1 file changed, 96 deletions(-) delete mode 100644 plugins/inputs/win_services/req_analysis.md diff --git a/plugins/inputs/win_services/req_analysis.md b/plugins/inputs/win_services/req_analysis.md deleted file mode 100644 index 0a68e96e0b04f..0000000000000 --- a/plugins/inputs/win_services/req_analysis.md +++ /dev/null @@ -1,96 +0,0 @@ -# Win_services telegraf input plugin analysis -This is an analysis of a telegraf feature requirement demanding a plugin for collecting windows services info, -originally requested in [telegraf issue #2714](https://github.com/influxdata/telegraf/issues/2714) - -## Feature -### Use Cases -- Admin needs to monitor the states of selected windows services on the host, along with a few additional properties (display name, start-up mode) -- Admin wants to query info of monitored services -- Admin wants to query what services have defined properties - -### Feature requirements - * telegraf input plugin - * store service name, display name, state and startup mode - * configure what services should be monitored - -### Platform requirements - * WindowsXP and higher - * Windows Server 2003 and higher - - Defined by Windows Service API, used for querying services, availability - -### Admin rights -Admin privileges are required to read service info. However, as telegraf mostly runs as a service under the Local System account, it should be no problem. - -### Deployment -Feature request mentions the monitoring of 5000 servers. This either means: -* deploying telegraf on each monitored host, which is the preferred option, as the other plugins can be used to monitor other stuff on the host, -* plugin has to monitor multiple servers, which would lead to a more complex plugin (service input plugin) along with complex configuration - -## Implementation -### Storing service info -#### Measurement -There are two options to define what a measurement might be -1. Store all service info in single measurement, e.g. win_services, configurable -2. Store service info per service - -Option 1. has the biggest benefit in that a user can easily query info about all services, e.g. all services in a stopped state, but this leads to a lot of data in a single measurement, as service measurements have to have the same schema for it to make sense to use this. - -Option 2. diversifies the data but makes it difficult to query multiple services - -_Q: What is the best practise? I would prefer option 1.. Also, it could be configurable._ - -#### Properties - -Basic properties to monitor and store, as initially requested, are: - * service name - * display name - * startup mode - * actual state - - There are also additional properties available(see [SERVICE_STATUS_PROCESS](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685992(v=vs.85).aspx) and [QUERY_SERVICE_CONFIG](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684950(v=vs.85).aspx) ), but these are quite advanced and not so suitable for monitoring. - - Display name and startup mode are rather static properties and they will change rarely, but as most probable output is influxdb it will handle this with compaction. - -According to the use cases, conditions in queries can be based on _service name_ or a property (state, startup mode). -Services most probably won't be filtered according to display name. - -So the mapping to a tag or a field would be: - -* **Field** = actual state, startup mode, -* **Tag** = service name, displayName - - Let's use the following key name and 'types': - - Property|Key | Type - ---- |----- | --- - service name| service_name | string - display name| display_name | string - startup mode| startup_mode | string - actual state| state | string - -The keys _startupMode_ and _state_ will be a human readable representation of the attribute. - - ### Configuration - * User must be able to set what services will be monitored: - ```` - # Case-insensitive name of services to monitor. Empty of all services - service_names = [ - "LanmanServer", - "TermService" - ] - ```` -Services in examples should be available on all Windows editions and versions. - - ### Storing Errors - There are basically two possible errors: - * Invalid service name given in configuration - * A service requires special privileges - - This should be reported as a warning, not as a complete error of the Gather function - - As stated by Daniel, best practice is to use Accumulator.AddError and log it every time. Telegraf should handle multiple instances of the same errors. - - ### Caching - Most service info is almost static and it could be cached. But as all the info about requested services that Windows Service Manager stores in memory, even a full listing, takes just 8ms on Windows 10 on a Core i5 (2 cores) laptop, - so caching seems like overhead. From 4a0d3e8e32e3faae1b83b38be9f70df4d10321b6 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Mon, 17 Jul 2017 11:17:34 +0200 Subject: [PATCH 26/44] Added important note --- plugins/inputs/win_services/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index bc15b3dd05379..bacc825c7966f 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -1,6 +1,7 @@ # Telegraf Plugin: win_services Input plugin to report Windows services info: service name, display name, state, startup mode +It requires that Telegraf must be running under administrator privileges. ### Configuration: ```toml From 75407ccf9ecc99b875a2de26950142c189aa7335 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Mon, 17 Jul 2017 11:19:45 +0200 Subject: [PATCH 27/44] Typo --- plugins/inputs/win_services/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index bacc825c7966f..7c43103bc917f 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -1,7 +1,7 @@ # Telegraf Plugin: win_services Input plugin to report Windows services info: service name, display name, state, startup mode -It requires that Telegraf must be running under administrator privileges. +It requires that Telegraf must be running under the administrator privileges. ### Configuration: ```toml From d5708947e09126cec1da68b04ea68af66a87a8a4 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 18 Jul 2017 10:59:54 +0200 Subject: [PATCH 28/44] Reformatted with go fmt --- plugins/inputs/win_services/win_services.go | 160 ++++++++++---------- 1 file changed, 79 insertions(+), 81 deletions(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index 2f830fda09d20..a28985d591d0f 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -3,10 +3,10 @@ package win_services import ( + "fmt" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" "golang.org/x/sys/windows/svc/mgr" - "fmt" ) var sampleConfig = ` @@ -24,30 +24,29 @@ type Win_Services struct { } type ServiceInfo struct { - ServiceName string - DisplayName string - State int - StartUpMode int - Error error + ServiceName string + DisplayName string + State int + StartUpMode int + Error error } -var ServiceStatesMap = map [int]string{ - 0x00000001: "stopped", - 0x00000002: "start_pending", - 0x00000003: "stop_pending", - 0x00000004: "running", - 0x00000005: "continue_pending", - 0x00000006: "pause_pending", - 0x00000007: "paused", +var ServiceStatesMap = map[int]string{ + 0x00000001: "stopped", + 0x00000002: "start_pending", + 0x00000003: "stop_pending", + 0x00000004: "running", + 0x00000005: "continue_pending", + 0x00000006: "pause_pending", + 0x00000007: "paused", } -var ServiceStartupModeMap = map [int]string{ - 0x00000000: "boot_start", - 0x00000001: "system_start", - 0x00000002: "auto_start", - 0x00000003: "demand_start", - 0x00000004: "disabled", - +var ServiceStartupModeMap = map[int]string{ + 0x00000000: "boot_start", + 0x00000001: "system_start", + 0x00000002: "auto_start", + 0x00000003: "demand_start", + 0x00000004: "disabled", } func (m *Win_Services) Description() string { @@ -58,79 +57,78 @@ func (m *Win_Services) SampleConfig() string { return sampleConfig } - func (m *Win_Services) Gather(acc telegraf.Accumulator) error { - serviceInfos, err := listServices(m.ServiceNames) + serviceInfos, err := listServices(m.ServiceNames) - if err != nil { - return err - } + if err != nil { + return err + } - for _, service := range serviceInfos { - if service.Error == nil { - fields := make(map[string]interface{}) - tags := make(map[string]string) + for _, service := range serviceInfos { + if service.Error == nil { + fields := make(map[string]interface{}) + tags := make(map[string]string) - tags["display_name"] = service.DisplayName - tags["service_name"] = service.ServiceName + tags["display_name"] = service.DisplayName + tags["service_name"] = service.ServiceName - fields["state"] = ServiceStatesMap[service.State] - fields["startup_mode"] = ServiceStartupModeMap[service.StartUpMode] + fields["state"] = ServiceStatesMap[service.State] + fields["startup_mode"] = ServiceStartupModeMap[service.StartUpMode] - acc.AddFields("win_services", fields, tags) - } else { - acc.AddError(service.Error) - } - } + acc.AddFields("win_services", fields, tags) + } else { + acc.AddError(service.Error) + } + } return nil } func listServices(userServices []string) ([]ServiceInfo, error) { - scmgr, err := mgr.Connect() - if err != nil { - return nil, fmt.Errorf("Could not open service manager: %s", err) - } - defer scmgr.Disconnect() - - var serviceNames []string - if len(userServices) == 0 { - //Listing service names from system - serviceNames, err = scmgr.ListServices() - if err != nil { - return nil, fmt.Errorf("Could not list services: %s", err) - } - } else { - serviceNames = userServices - } - serviceInfos := make([]ServiceInfo, len(serviceNames)) - - for i, srvName := range serviceNames { - serviceInfos[i].ServiceName = srvName - srv, err := scmgr.OpenService(srvName) - if err != nil { - serviceInfos[i].Error = fmt.Errorf("Could not open service '%s': %s",srvName, err) - continue - } - srvStatus, err := srv.Query() - if err == nil { - serviceInfos[i].State = int(srvStatus.State) - } else { - serviceInfos[i].Error = fmt.Errorf("Could not query service '%s': %s",srvName, err) - } - - srvCfg, err := srv.Config() - if err == nil { - serviceInfos[i].DisplayName = srvCfg.DisplayName - serviceInfos[i].StartUpMode = int(srvCfg.StartType) - } else { - serviceInfos[i].Error = fmt.Errorf("Could not get config of service '%s': %s", srvName, err) - } - srv.Close() - } - return serviceInfos, nil + scmgr, err := mgr.Connect() + if err != nil { + return nil, fmt.Errorf("Could not open service manager: %s", err) + } + defer scmgr.Disconnect() + + var serviceNames []string + if len(userServices) == 0 { + //Listing service names from system + serviceNames, err = scmgr.ListServices() + if err != nil { + return nil, fmt.Errorf("Could not list services: %s", err) + } + } else { + serviceNames = userServices + } + serviceInfos := make([]ServiceInfo, len(serviceNames)) + + for i, srvName := range serviceNames { + serviceInfos[i].ServiceName = srvName + srv, err := scmgr.OpenService(srvName) + if err != nil { + serviceInfos[i].Error = fmt.Errorf("Could not open service '%s': %s", srvName, err) + continue + } + srvStatus, err := srv.Query() + if err == nil { + serviceInfos[i].State = int(srvStatus.State) + } else { + serviceInfos[i].Error = fmt.Errorf("Could not query service '%s': %s", srvName, err) + } + + srvCfg, err := srv.Config() + if err == nil { + serviceInfos[i].DisplayName = srvCfg.DisplayName + serviceInfos[i].StartUpMode = int(srvCfg.StartType) + } else { + serviceInfos[i].Error = fmt.Errorf("Could not get config of service '%s': %s", srvName, err) + } + srv.Close() + } + return serviceInfos, nil } func init() { From 3b3106fdb9b49a1e5bd526a52359c2757ada0ba1 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 18 Jul 2017 11:31:07 +0200 Subject: [PATCH 29/44] Reformated with go fmt --- .../inputs/win_services/win_services_test.go | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/plugins/inputs/win_services/win_services_test.go b/plugins/inputs/win_services/win_services_test.go index b80b6dbd3f71a..57b368d9e728b 100644 --- a/plugins/inputs/win_services/win_services_test.go +++ b/plugins/inputs/win_services/win_services_test.go @@ -7,8 +7,8 @@ import ( "github.com/influxdata/telegraf/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/sys/windows/svc/mgr" "testing" - "golang.org/x/sys/windows/svc/mgr" ) var InvalidServices = []string{"XYZ1@", "ZYZ@", "SDF_@#"} @@ -25,9 +25,9 @@ func TestList(t *testing.T) { } func TestEmptyList(t *testing.T) { - services, err := listServices([]string {}) - require.NoError(t, err) - assert.Condition(t, func () bool { return len(services) > 20}, "Too few service") + services, err := listServices([]string{}) + require.NoError(t, err) + assert.Condition(t, func() bool { return len(services) > 20 }, "Too few service") } func TestListEr(t *testing.T) { @@ -41,22 +41,22 @@ func TestListEr(t *testing.T) { } func TestGather(t *testing.T) { - ws := &Win_Services{KnownServices} - assert.Len(t, ws.ServiceNames, 2, "Different number of services") - var acc testutil.Accumulator - require.NoError(t, ws.Gather(&acc)) - assert.Len(t, acc.Errors, 0, "There should be no errors after gather") + ws := &Win_Services{KnownServices} + assert.Len(t, ws.ServiceNames, 2, "Different number of services") + var acc testutil.Accumulator + require.NoError(t, ws.Gather(&acc)) + assert.Len(t, acc.Errors, 0, "There should be no errors after gather") - for i := 0; i < 2; i++ { - fields := make(map[string]interface{}) - tags := make(map[string]string) - si := getServiceInfo(KnownServices[i]) - fields["state"] = ServiceStatesMap[si.State] - fields["startup_mode"] = ServiceStartupModeMap[si.StartUpMode] - tags["service_name"] = si.ServiceName - tags["display_name"] = si.DisplayName - acc.AssertContainsTaggedFields(t, "win_services", fields, tags) - } + for i := 0; i < 2; i++ { + fields := make(map[string]interface{}) + tags := make(map[string]string) + si := getServiceInfo(KnownServices[i]) + fields["state"] = ServiceStatesMap[si.State] + fields["startup_mode"] = ServiceStartupModeMap[si.StartUpMode] + tags["service_name"] = si.ServiceName + tags["display_name"] = si.DisplayName + acc.AssertContainsTaggedFields(t, "win_services", fields, tags) + } } @@ -68,34 +68,34 @@ func TestGatherErrors(t *testing.T) { assert.Len(t, acc.Errors, 3, "There should be 3 errors after gather") } -func getServiceInfo(srvName string) (*ServiceInfo) { +func getServiceInfo(srvName string) *ServiceInfo { - scmgr, err := mgr.Connect() - if err != nil { - return nil - } - defer scmgr.Disconnect() + scmgr, err := mgr.Connect() + if err != nil { + return nil + } + defer scmgr.Disconnect() - srv, err := scmgr.OpenService(srvName) - if err != nil { - return nil - } - var si ServiceInfo - si.ServiceName = srvName - srvStatus, err := srv.Query() - if err == nil { - si.State = int(srvStatus.State) - } else { - si.Error = err - } + srv, err := scmgr.OpenService(srvName) + if err != nil { + return nil + } + var si ServiceInfo + si.ServiceName = srvName + srvStatus, err := srv.Query() + if err == nil { + si.State = int(srvStatus.State) + } else { + si.Error = err + } - srvCfg, err := srv.Config() - if err == nil { - si.DisplayName = srvCfg.DisplayName - si.StartUpMode = int(srvCfg.StartType) - } else { - si.Error = err - } - srv.Close() - return &si + srvCfg, err := srv.Config() + if err == nil { + si.DisplayName = srvCfg.DisplayName + si.StartUpMode = int(srvCfg.StartType) + } else { + si.Error = err + } + srv.Close() + return &si } From 721264af3da5c1b4d0bde73d6dad897bb88ef213 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 18 Jul 2017 11:40:00 +0200 Subject: [PATCH 30/44] - Implemented PR review feedback - Fixed few mistakes --- plugins/inputs/win_services/README.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index 7c43103bc917f..dfb8f128fd67b 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -1,5 +1,5 @@ # Telegraf Plugin: win_services -Input plugin to report Windows services info: service name, display name, state, startup mode +Input plugin to report Windows services info. It requires that Telegraf must be running under the administrator privileges. ### Configuration: @@ -19,7 +19,7 @@ It requires that Telegraf must be running under the administrator privileges. - state - startup_mode -The `state` tag can have the following values: +The `state` field can have the following values: * _stopped_ * _start_pending_ * _stop_pending_ @@ -28,7 +28,7 @@ The `state` tag can have the following values: * _pause_pending_ * _paused_ -The `startup_mode` tag can have the following values: +The `startup_mode` field can have the following values: * _boot_start_ * _system_start_ * _auto_start_ @@ -42,17 +42,9 @@ The `startup_mode` tag can have the following values: - display_name ### Example Output: - -Using the default configuration: - -When run with: -``` -E:\Telegraf>telegraf.exe -config telegraf.conf -test -``` -It produces: ``` * Plugin: inputs.win_services, Collection 1 -> win_services,host=WIN2008R2H401,display_name=Server,service_name=LanmanServer state="running",startup_mode="auto_start" 15 00040669000000000 +> win_services,host=WIN2008R2H401,display_name=Server,service_name=LanmanServer state="running",startup_mode="auto_start" 1500040669000000000 > win_services,display_name=Remote\ Desktop\ Services,service_name=TermService,host=WIN2008R2H401 state="stopped",startup_mode="demand_start" 1500040669000000000 ``` ### TICK Scripts @@ -72,5 +64,5 @@ stream .crit(lambda: "state" != 'running') .stateChangesOnly() .message('Service {{ index .Tags "service_name" }} on Host {{ index .Tags "host" }} is {{ index .Fields "state" }} ') - .post('http://localhost:666/alert/cpu') + .post('http://localhost:666/alert/service') ``` From e0dbcabf9c0f27bdf533f2dc0d19161cd38f5425 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 19 Jul 2017 14:08:35 +0200 Subject: [PATCH 31/44] Changed state and startup mode fields from string to int --- plugins/inputs/win_services/README.md | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/plugins/inputs/win_services/README.md b/plugins/inputs/win_services/README.md index dfb8f128fd67b..4aa9e6b867deb 100644 --- a/plugins/inputs/win_services/README.md +++ b/plugins/inputs/win_services/README.md @@ -16,24 +16,24 @@ It requires that Telegraf must be running under the administrator privileges. ### Measurements & Fields: - win_services - - state - - startup_mode + - state : integer + - startup_mode : integer The `state` field can have the following values: -* _stopped_ -* _start_pending_ -* _stop_pending_ -* _running_ -* _continue_pending_ -* _pause_pending_ -* _paused_ +- 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: -* _boot_start_ -* _system_start_ -* _auto_start_ -* _demand_start_ -* _disabled_ +- 0 - boot start +- 1 - system start +- 2 - auto start +- 3 - demand start +- 4 - disabled ### Tags: @@ -44,8 +44,8 @@ The `startup_mode` field can have the following values: ### Example Output: ``` * Plugin: inputs.win_services, Collection 1 -> win_services,host=WIN2008R2H401,display_name=Server,service_name=LanmanServer state="running",startup_mode="auto_start" 1500040669000000000 -> win_services,display_name=Remote\ Desktop\ Services,service_name=TermService,host=WIN2008R2H401 state="stopped",startup_mode="demand_start" 1500040669000000000 +> 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 @@ -61,8 +61,8 @@ stream .measurement('win_services') .groupBy('host','service_name') |alert() - .crit(lambda: "state" != 'running') + .crit(lambda: "state" != 4) .stateChangesOnly() - .message('Service {{ index .Tags "service_name" }} on Host {{ index .Tags "host" }} is {{ index .Fields "state" }} ') + .message('Service {{ index .Tags "service_name" }} on Host {{ index .Tags "host" }} is in state {{ index .Fields "state" }} ') .post('http://localhost:666/alert/service') ``` From 86b989ac9727218b15ecce44dee43e5ce9acd3cd Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 19 Jul 2017 14:11:28 +0200 Subject: [PATCH 32/44] - Changed state and startup mode fields from string to int - Extended values validation - Reporting first found service error --- plugins/inputs/win_services/win_services.go | 82 +++++++++++++++------ 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index a28985d591d0f..82ba604d174c1 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -17,7 +17,7 @@ var sampleConfig = ` ] ` -var description = "Input plugin to report Windows services info: service name, display name, state, startup mode" +var description = "Input plugin to report Windows services info." type Win_Services struct { ServiceNames []string `toml:"service_names"` @@ -73,8 +73,8 @@ func (m *Win_Services) Gather(acc telegraf.Accumulator) error { tags["display_name"] = service.DisplayName tags["service_name"] = service.ServiceName - fields["state"] = ServiceStatesMap[service.State] - fields["startup_mode"] = ServiceStartupModeMap[service.StartUpMode] + fields["state"] = service.State + fields["startup_mode"] = service.StartUpMode acc.AddFields("win_services", fields, tags) } else { @@ -86,7 +86,6 @@ func (m *Win_Services) Gather(acc telegraf.Accumulator) error { } func listServices(userServices []string) ([]ServiceInfo, error) { - scmgr, err := mgr.Connect() if err != nil { return nil, fmt.Errorf("Could not open service manager: %s", err) @@ -106,29 +105,66 @@ func listServices(userServices []string) ([]ServiceInfo, error) { serviceInfos := make([]ServiceInfo, len(serviceNames)) for i, srvName := range serviceNames { - serviceInfos[i].ServiceName = srvName - srv, err := scmgr.OpenService(srvName) - if err != nil { - serviceInfos[i].Error = fmt.Errorf("Could not open service '%s': %s", srvName, err) - continue - } - srvStatus, err := srv.Query() - if err == nil { - serviceInfos[i].State = int(srvStatus.State) - } else { - serviceInfos[i].Error = fmt.Errorf("Could not query service '%s': %s", srvName, err) + serviceInfos[i] = collectServiceInfo(scmgr, srvName) + } + + return serviceInfos, nil +} + +func collectServiceInfo(scmgr *mgr.Mgr, serviceName string) (serviceInfo ServiceInfo) { + + serviceInfo.ServiceName = serviceName + srv, err := scmgr.OpenService(serviceName) + if err != nil { + serviceInfo.Error = fmt.Errorf("Could not open service '%s': %s", serviceName, err) + return + } + defer srv.Close() + + //While getting service info there could a theoretically a lot of errors on different places. + //However in reality if there is a problem with a service then usually openService fails and if it passes, other calls will most probably be ok + //So, following error checking is just for sake + srvStatus, err := srv.Query() + if err == nil { + state := int(srvStatus.State) + if !checkState(state) { + serviceInfo.Error = fmt.Errorf("Uknown state of Service %s: %d", serviceName, state) + //finish collecting info on first found error + return } + serviceInfo.State = state + } else { + serviceInfo.Error = fmt.Errorf("Could not query service '%s': %s", serviceName, err) + //finish collecting info on first found error + return + } - srvCfg, err := srv.Config() - if err == nil { - serviceInfos[i].DisplayName = srvCfg.DisplayName - serviceInfos[i].StartUpMode = int(srvCfg.StartType) - } else { - serviceInfos[i].Error = fmt.Errorf("Could not get config of service '%s': %s", srvName, err) + srvCfg, err := srv.Config() + if err == nil { + startupMode := int(srvCfg.StartType) + if !checkStartupMode(startupMode) { + serviceInfo.Error = fmt.Errorf("Uknown startup mode of Service %s: %d", serviceName, startupMode) + //finish collecting info on first found error + return } - srv.Close() + serviceInfo.DisplayName = srvCfg.DisplayName + serviceInfo.StartUpMode = startupMode + } else { + serviceInfo.Error = fmt.Errorf("Could not get config of service '%s': %s", serviceName, err) } - return serviceInfos, nil + return +} + +//returns true of state is in valid range +func checkState(state int) bool { + _, ok := ServiceStatesMap[state] + return ok +} + +//returns true of startup mode is in valid range +func checkStartupMode(startupMode int) bool { + _, ok := ServiceStartupModeMap[startupMode] + return ok } func init() { From a96932c47085fbadc85d85160c56f960d5c7f68d Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 19 Jul 2017 14:14:20 +0200 Subject: [PATCH 33/44] Changed state and startup mode fields from string to int --- plugins/inputs/win_services/win_services_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/inputs/win_services/win_services_test.go b/plugins/inputs/win_services/win_services_test.go index 57b368d9e728b..d8023d02829e0 100644 --- a/plugins/inputs/win_services/win_services_test.go +++ b/plugins/inputs/win_services/win_services_test.go @@ -51,8 +51,8 @@ func TestGather(t *testing.T) { fields := make(map[string]interface{}) tags := make(map[string]string) si := getServiceInfo(KnownServices[i]) - fields["state"] = ServiceStatesMap[si.State] - fields["startup_mode"] = ServiceStartupModeMap[si.StartUpMode] + fields["state"] = int(si.State) + fields["startup_mode"] = int(si.StartUpMode) tags["service_name"] = si.ServiceName tags["display_name"] = si.DisplayName acc.AssertContainsTaggedFields(t, "win_services", fields, tags) From 5eda56574f8a728996729b471443f74ebcf38ab8 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 1 Aug 2017 10:08:18 +0200 Subject: [PATCH 34/44] Small improvements based on PR discussion: - better name of main struct - better methods description - removed useless checking - added empty service description checking --- plugins/inputs/win_services/win_services.go | 66 ++++----------------- 1 file changed, 13 insertions(+), 53 deletions(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index 82ba604d174c1..2ab67753aeaab 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -19,7 +19,7 @@ var sampleConfig = ` var description = "Input plugin to report Windows services info." -type Win_Services struct { +type WinServices struct { ServiceNames []string `toml:"service_names"` } @@ -31,33 +31,15 @@ type ServiceInfo struct { Error error } -var ServiceStatesMap = map[int]string{ - 0x00000001: "stopped", - 0x00000002: "start_pending", - 0x00000003: "stop_pending", - 0x00000004: "running", - 0x00000005: "continue_pending", - 0x00000006: "pause_pending", - 0x00000007: "paused", -} - -var ServiceStartupModeMap = map[int]string{ - 0x00000000: "boot_start", - 0x00000001: "system_start", - 0x00000002: "auto_start", - 0x00000003: "demand_start", - 0x00000004: "disabled", -} - -func (m *Win_Services) Description() string { +func (m *WinServices) Description() string { return description } -func (m *Win_Services) SampleConfig() string { +func (m *WinServices) SampleConfig() string { return sampleConfig } -func (m *Win_Services) Gather(acc telegraf.Accumulator) error { +func (m *WinServices) Gather(acc telegraf.Accumulator) error { serviceInfos, err := listServices(m.ServiceNames) @@ -70,7 +52,10 @@ func (m *Win_Services) Gather(acc telegraf.Accumulator) error { fields := make(map[string]interface{}) tags := make(map[string]string) - tags["display_name"] = service.DisplayName + //display name could be empty, but still valid service + if len(service.DisplayName) > 0 { + tags["display_name"] = service.DisplayName + } tags["service_name"] = service.ServiceName fields["state"] = service.State @@ -85,6 +70,7 @@ func (m *Win_Services) Gather(acc telegraf.Accumulator) error { return nil } +//listServices gathers info about given services. If userServices is empty, it return info about all services on current Windows host. Any a critical error is returned. func listServices(userServices []string) ([]ServiceInfo, error) { scmgr, err := mgr.Connect() if err != nil { @@ -111,6 +97,7 @@ func listServices(userServices []string) ([]ServiceInfo, error) { return serviceInfos, nil } +//collectServiceInfo gathers info about a service from WindowsAPI func collectServiceInfo(scmgr *mgr.Mgr, serviceName string) (serviceInfo ServiceInfo) { serviceInfo.ServiceName = serviceName @@ -121,18 +108,9 @@ func collectServiceInfo(scmgr *mgr.Mgr, serviceName string) (serviceInfo Service } defer srv.Close() - //While getting service info there could a theoretically a lot of errors on different places. - //However in reality if there is a problem with a service then usually openService fails and if it passes, other calls will most probably be ok - //So, following error checking is just for sake srvStatus, err := srv.Query() if err == nil { - state := int(srvStatus.State) - if !checkState(state) { - serviceInfo.Error = fmt.Errorf("Uknown state of Service %s: %d", serviceName, state) - //finish collecting info on first found error - return - } - serviceInfo.State = state + serviceInfo.State = int(srvStatus.State) } else { serviceInfo.Error = fmt.Errorf("Could not query service '%s': %s", serviceName, err) //finish collecting info on first found error @@ -141,32 +119,14 @@ func collectServiceInfo(scmgr *mgr.Mgr, serviceName string) (serviceInfo Service srvCfg, err := srv.Config() if err == nil { - startupMode := int(srvCfg.StartType) - if !checkStartupMode(startupMode) { - serviceInfo.Error = fmt.Errorf("Uknown startup mode of Service %s: %d", serviceName, startupMode) - //finish collecting info on first found error - return - } serviceInfo.DisplayName = srvCfg.DisplayName - serviceInfo.StartUpMode = startupMode + serviceInfo.StartUpMode = int(srvCfg.StartType) } else { serviceInfo.Error = fmt.Errorf("Could not get config of service '%s': %s", serviceName, err) } return } -//returns true of state is in valid range -func checkState(state int) bool { - _, ok := ServiceStatesMap[state] - return ok -} - -//returns true of startup mode is in valid range -func checkStartupMode(startupMode int) bool { - _, ok := ServiceStartupModeMap[startupMode] - return ok -} - func init() { - inputs.Add("win_services", func() telegraf.Input { return &Win_Services{} }) + inputs.Add("win_services", func() telegraf.Input { return &WinServices{} }) } From 1966f4213de47eccbb74964e50abe7c489dcb334 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 1 Aug 2017 11:53:49 +0200 Subject: [PATCH 35/44] Changing existing tests, which test real service manager, to integration tests --- .../win_services_integration_test.go | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 plugins/inputs/win_services/win_services_integration_test.go diff --git a/plugins/inputs/win_services/win_services_integration_test.go b/plugins/inputs/win_services/win_services_integration_test.go new file mode 100644 index 0000000000000..9753bc3508675 --- /dev/null +++ b/plugins/inputs/win_services/win_services_integration_test.go @@ -0,0 +1,111 @@ +// +build windows + +//these tests must be run under administrator account +package win_services + +import ( + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/windows/svc/mgr" + "testing" +) + +var InvalidServices = []string{"XYZ1@", "ZYZ@", "SDF_@#"} +var KnownServices = []string{"LanmanServer", "TermService"} + +func TestList(t *testing.T) { + if !testing.Short() { + services, err := listServices(&MgProvider{}, KnownServices) + require.NoError(t, err) + assert.Len(t, services, 2, "Different number of services") + assert.Equal(t, services[0].ServiceName, KnownServices[0]) + assert.Nil(t, services[0].Error) + assert.Equal(t, services[1].ServiceName, KnownServices[1]) + assert.Nil(t, services[1].Error) + } +} + +func TestEmptyList(t *testing.T) { + if !testing.Short() { + services, err := listServices(&MgProvider{}, []string{}) + require.NoError(t, err) + assert.Condition(t, func() bool { return len(services) > 20 }, "Too few service") + } +} + +func TestListEr(t *testing.T) { + if !testing.Short() { + services, err := listServices(&MgProvider{}, InvalidServices) + require.NoError(t, err) + assert.Len(t, services, 3, "Different number of services") + for i := 0; i < 3; i++ { + assert.Equal(t, services[i].ServiceName, InvalidServices[i]) + assert.NotNil(t, services[i].Error) + } + } +} + +func TestGather(t *testing.T) { + if !testing.Short() { + ws := &WinServices{KnownServices, &MgProvider{}} + assert.Len(t, ws.ServiceNames, 2, "Different number of services") + var acc testutil.Accumulator + require.NoError(t, ws.Gather(&acc)) + assert.Len(t, acc.Errors, 0, "There should be no errors after gather") + + for i := 0; i < 2; i++ { + fields := make(map[string]interface{}) + tags := make(map[string]string) + si := getServiceInfo(KnownServices[i]) + fields["state"] = int(si.State) + fields["startup_mode"] = int(si.StartUpMode) + tags["service_name"] = si.ServiceName + tags["display_name"] = si.DisplayName + acc.AssertContainsTaggedFields(t, "win_services", fields, tags) + } + } + +} + +func TestGatherErrors(t *testing.T) { + if !testing.Short() { + ws := &WinServices{InvalidServices, &MgProvider{}} + assert.Len(t, ws.ServiceNames, 3, "Different number of services") + var acc testutil.Accumulator + require.NoError(t, ws.Gather(&acc)) + assert.Len(t, acc.Errors, 3, "There should be 3 errors after gather") + } +} + +func getServiceInfo(srvName string) *ServiceInfo { + + scmgr, err := mgr.Connect() + if err != nil { + return nil + } + defer scmgr.Disconnect() + + srv, err := scmgr.OpenService(srvName) + if err != nil { + return nil + } + var si ServiceInfo + si.ServiceName = srvName + srvStatus, err := srv.Query() + if err == nil { + si.State = int(srvStatus.State) + } else { + si.Error = err + } + + srvCfg, err := srv.Config() + if err == nil { + si.DisplayName = srvCfg.DisplayName + si.StartUpMode = int(srvCfg.StartType) + } else { + si.Error = err + } + srv.Close() + return &si +} From ddbdcfc8c4c740364291b43916be11277b2d3f12 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 1 Aug 2017 16:02:53 +0200 Subject: [PATCH 36/44] Added interfaces and real impl wrappers to enable unit testing --- plugins/inputs/win_services/win_services.go | 55 +++++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index 2ab67753aeaab..b8a5b0bafd098 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" + "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/mgr" ) @@ -21,6 +22,7 @@ var description = "Input plugin to report Windows services info." type WinServices struct { ServiceNames []string `toml:"service_names"` + mgrProvider WinServiceManagerProvider } type ServiceInfo struct { @@ -31,6 +33,49 @@ type ServiceInfo struct { Error error } +type WinService interface { + Close() error + Config() (mgr.Config, error) + Query() (svc.Status, error) +} + +type WinServiceManagerProvider interface { + Connect() (WinServiceManager, error) +} + +type WinServiceManager interface { + Disconnect() error + OpenService(name string) (WinService, error) + ListServices() ([]string, error) +} + +type WinSvcMgr struct { + realMgr *mgr.Mgr +} + +func (m *WinSvcMgr) Disconnect() error { + return m.realMgr.Disconnect() +} + +func (m *WinSvcMgr) OpenService(name string) (WinService, error) { + return m.realMgr.OpenService(name) +} +func (m *WinSvcMgr) ListServices() ([]string, error) { + return m.realMgr.ListServices() +} + +type MgProvider struct { +} + +func (rmr *MgProvider) Connect() (WinServiceManager, error) { + scmgr, err := mgr.Connect() + if err != nil { + return nil, err + } else { + return &WinSvcMgr{scmgr}, nil + } +} + func (m *WinServices) Description() string { return description } @@ -41,7 +86,7 @@ func (m *WinServices) SampleConfig() string { func (m *WinServices) Gather(acc telegraf.Accumulator) error { - serviceInfos, err := listServices(m.ServiceNames) + serviceInfos, err := listServices(m.mgrProvider, m.ServiceNames) if err != nil { return err @@ -71,8 +116,8 @@ func (m *WinServices) Gather(acc telegraf.Accumulator) error { } //listServices gathers info about given services. If userServices is empty, it return info about all services on current Windows host. Any a critical error is returned. -func listServices(userServices []string) ([]ServiceInfo, error) { - scmgr, err := mgr.Connect() +func listServices(mgrProv WinServiceManagerProvider, userServices []string) ([]ServiceInfo, error) { + scmgr, err := mgrProv.Connect() if err != nil { return nil, fmt.Errorf("Could not open service manager: %s", err) } @@ -98,7 +143,7 @@ func listServices(userServices []string) ([]ServiceInfo, error) { } //collectServiceInfo gathers info about a service from WindowsAPI -func collectServiceInfo(scmgr *mgr.Mgr, serviceName string) (serviceInfo ServiceInfo) { +func collectServiceInfo(scmgr WinServiceManager, serviceName string) (serviceInfo ServiceInfo) { serviceInfo.ServiceName = serviceName srv, err := scmgr.OpenService(serviceName) @@ -128,5 +173,5 @@ func collectServiceInfo(scmgr *mgr.Mgr, serviceName string) (serviceInfo Service } func init() { - inputs.Add("win_services", func() telegraf.Input { return &WinServices{} }) + inputs.Add("win_services", func() telegraf.Input { return &WinServices{mgrProvider: &MgProvider{}} }) } From 8c755624beb4d583b0bd6ce35a94bd5021cc0f7a Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 1 Aug 2017 16:03:50 +0200 Subject: [PATCH 37/44] Added data driven unit tests with mock implementation --- .../inputs/win_services/win_services_test.go | 212 ++++++++++++------ 1 file changed, 145 insertions(+), 67 deletions(-) diff --git a/plugins/inputs/win_services/win_services_test.go b/plugins/inputs/win_services/win_services_test.go index d8023d02829e0..c13e9a5fbb915 100644 --- a/plugins/inputs/win_services/win_services_test.go +++ b/plugins/inputs/win_services/win_services_test.go @@ -4,98 +4,176 @@ package win_services import ( + "errors" + "fmt" "github.com/influxdata/telegraf/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/mgr" "testing" ) -var InvalidServices = []string{"XYZ1@", "ZYZ@", "SDF_@#"} -var KnownServices = []string{"LanmanServer", "TermService"} - -func TestList(t *testing.T) { - services, err := listServices(KnownServices) - require.NoError(t, err) - assert.Len(t, services, 2, "Different number of services") - assert.Equal(t, services[0].ServiceName, KnownServices[0]) - assert.Nil(t, services[0].Error) - assert.Equal(t, services[1].ServiceName, KnownServices[1]) - assert.Nil(t, services[1].Error) +type TestData struct { + //collection that will be returned in ListServices if service arrays passed into WinServices constructor is empty + queryServiceList []string + mgrConnectError error + mgrListServicesError error + services []ServiceTestInfo } -func TestEmptyList(t *testing.T) { - services, err := listServices([]string{}) - require.NoError(t, err) - assert.Condition(t, func() bool { return len(services) > 20 }, "Too few service") +type ServiceTestInfo struct { + serviceOpenError error + serviceQueryError error + serviceConfigError error + serviceName string + displayName string + state int + startUpMode int } -func TestListEr(t *testing.T) { - services, err := listServices(InvalidServices) - require.NoError(t, err) - assert.Len(t, services, 3, "Different number of services") - for i := 0; i < 3; i++ { - assert.Equal(t, services[i].ServiceName, InvalidServices[i]) - assert.NotNil(t, services[i].Error) - } +type FakeSvcMgr struct { + testData TestData } -func TestGather(t *testing.T) { - ws := &Win_Services{KnownServices} - assert.Len(t, ws.ServiceNames, 2, "Different number of services") - var acc testutil.Accumulator - require.NoError(t, ws.Gather(&acc)) - assert.Len(t, acc.Errors, 0, "There should be no errors after gather") +func (m *FakeSvcMgr) Disconnect() error { + return nil +} - for i := 0; i < 2; i++ { - fields := make(map[string]interface{}) - tags := make(map[string]string) - si := getServiceInfo(KnownServices[i]) - fields["state"] = int(si.State) - fields["startup_mode"] = int(si.StartUpMode) - tags["service_name"] = si.ServiceName - tags["display_name"] = si.DisplayName - acc.AssertContainsTaggedFields(t, "win_services", fields, tags) +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 + } else { + return &FakeWinSvc{s}, nil + } + } } - + return nil, fmt.Errorf("Cannot find service %s", name) } -func TestGatherErrors(t *testing.T) { - ws := &Win_Services{InvalidServices} - assert.Len(t, ws.ServiceNames, 3, "Different number of services") - var acc testutil.Accumulator - require.NoError(t, ws.Gather(&acc)) - assert.Len(t, acc.Errors, 3, "There should be 3 errors after gather") +func (m *FakeSvcMgr) ListServices() ([]string, error) { + if m.testData.mgrListServicesError != nil { + return nil, m.testData.mgrListServicesError + } else { + return m.testData.queryServiceList, nil + } } -func getServiceInfo(srvName string) *ServiceInfo { +type FakeMgProvider struct { + testData TestData +} - scmgr, err := mgr.Connect() - if err != nil { - return nil +func (m *FakeMgProvider) Connect() (WinServiceManager, error) { + if m.testData.mgrConnectError != nil { + return nil, m.testData.mgrConnectError + } else { + return &FakeSvcMgr{m.testData}, nil } - defer scmgr.Disconnect() +} + +type FakeWinSvc struct { + testData ServiceTestInfo +} - srv, err := scmgr.OpenService(srvName) - if err != nil { - return nil +func (m *FakeWinSvc) Close() error { + return nil +} +func (m *FakeWinSvc) Config() (mgr.Config, error) { + if m.testData.serviceConfigError != nil { + return mgr.Config{}, m.testData.serviceConfigError + } else { + return mgr.Config{0, uint32(m.testData.startUpMode), 0, "", "", 0, nil, m.testData.serviceName, m.testData.displayName, "", ""}, nil } - var si ServiceInfo - si.ServiceName = srvName - srvStatus, err := srv.Query() - if err == nil { - si.State = int(srvStatus.State) +} +func (m *FakeWinSvc) Query() (svc.Status, error) { + if m.testData.serviceQueryError != nil { + return svc.Status{}, m.testData.serviceQueryError } else { - si.Error = err + return svc.Status{svc.State(m.testData.state), 0, 0, 0}, nil } +} - srvCfg, err := srv.Config() - if err == nil { - si.DisplayName = srvCfg.DisplayName - si.StartUpMode = int(srvCfg.StartType) - } else { - si.Error = err +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}, + }}, + {nil, nil, nil, []ServiceTestInfo{ + {errors.New("Fake srv open error"), nil, nil, "Fake service 1", "", 0, 0}, + }}, +} + +func TestBasicInfo(t *testing.T) { + + winServices := &WinServices{nil, &FakeMgProvider{testErrors[0]}} + assert.NotEmpty(t, winServices.SampleConfig()) + assert.NotEmpty(t, winServices.SampleConfig()) +} + +func TestMgrErrors(t *testing.T) { + //mgr.connect error + winServices := &WinServices{nil, &FakeMgProvider{testErrors[0]}} + var acc1 testutil.Accumulator + err := winServices.Gather(&acc1) + require.Error(t, err) + assert.Contains(t, err.Error(), testErrors[0].mgrConnectError.Error()) + + ////mgr.listServices error + winServices = &WinServices{nil, &FakeMgProvider{testErrors[1]}} + var acc2 testutil.Accumulator + err = winServices.Gather(&acc2) + require.Error(t, err) + assert.Contains(t, err.Error(), testErrors[1].mgrListServicesError.Error()) + + ////mgr.listServices error 2 + winServices = &WinServices{[]string{"Fake service 1"}, &FakeMgProvider{testErrors[3]}} + var acc3 testutil.Accumulator + err = winServices.Gather(&acc3) + require.NoError(t, err) + assert.Len(t, acc3.Errors, 1) + +} + +func TestServiceErrors(t *testing.T) { + winServices := &WinServices{nil, &FakeMgProvider{testErrors[2]}} + var acc1 testutil.Accumulator + require.NoError(t, winServices.Gather(&acc1)) + assert.Len(t, acc1.Errors, 3) + //open service error + assert.Contains(t, acc1.Errors[0].Error(), testErrors[2].services[0].serviceOpenError.Error()) + //query service error + assert.Contains(t, acc1.Errors[1].Error(), testErrors[2].services[1].serviceQueryError.Error()) + //config service error + assert.Contains(t, acc1.Errors[2].Error(), 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 TestGather2(t *testing.T) { + winServices := &WinServices{nil, &FakeMgProvider{testSimpleData[0]}} + var acc1 testutil.Accumulator + require.NoError(t, winServices.Gather(&acc1)) + assert.Len(t, acc1.Errors, 0, "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"] = int(s.state) + fields["startup_mode"] = int(s.startUpMode) + tags["service_name"] = s.serviceName + tags["display_name"] = s.displayName + acc1.AssertContainsTaggedFields(t, "win_services", fields, tags) } - srv.Close() - return &si + } From 5b71ba8ef83d457c936391fc6908bc5df8175544 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Tue, 1 Aug 2017 16:05:56 +0200 Subject: [PATCH 38/44] Fixed getter name --- plugins/inputs/win_services/win_services_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/win_services/win_services_test.go b/plugins/inputs/win_services/win_services_test.go index c13e9a5fbb915..49b6587c66ba8 100644 --- a/plugins/inputs/win_services/win_services_test.go +++ b/plugins/inputs/win_services/win_services_test.go @@ -112,7 +112,7 @@ func TestBasicInfo(t *testing.T) { winServices := &WinServices{nil, &FakeMgProvider{testErrors[0]}} assert.NotEmpty(t, winServices.SampleConfig()) - assert.NotEmpty(t, winServices.SampleConfig()) + assert.NotEmpty(t, winServices.Description()) } func TestMgrErrors(t *testing.T) { From 90857440f7eb3b7960ff63520c6d4ea0b56e7d2f Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 2 Aug 2017 11:08:19 +0200 Subject: [PATCH 39/44] Added documentation to types --- plugins/inputs/win_services/win_services.go | 52 ++++++++++++--------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/plugins/inputs/win_services/win_services.go b/plugins/inputs/win_services/win_services.go index b8a5b0bafd098..8e56a96d05caf 100644 --- a/plugins/inputs/win_services/win_services.go +++ b/plugins/inputs/win_services/win_services.go @@ -10,45 +10,26 @@ import ( "golang.org/x/sys/windows/svc/mgr" ) -var sampleConfig = ` - ## Names of the services to monitor. Leave empty to monitor all the available services on the host - service_names = [ - "LanmanServer", - "TermService", - ] -` - -var description = "Input plugin to report Windows services info." - -type WinServices struct { - ServiceNames []string `toml:"service_names"` - mgrProvider WinServiceManagerProvider -} - -type ServiceInfo struct { - ServiceName string - DisplayName string - State int - StartUpMode int - Error error -} - +//WinService provides interface for svc.Service type WinService interface { Close() error Config() (mgr.Config, error) Query() (svc.Status, error) } +//WinServiceManagerProvider sets interface for acquiring manager instance, like mgr.Mgr type WinServiceManagerProvider interface { Connect() (WinServiceManager, error) } +//WinServiceManager provides interface for mgr.Mgr type WinServiceManager interface { Disconnect() error OpenService(name string) (WinService, error) ListServices() ([]string, error) } +//WinSvcMgr is wrapper for mgr.Mgr implementing WinServiceManager interface type WinSvcMgr struct { realMgr *mgr.Mgr } @@ -64,6 +45,7 @@ func (m *WinSvcMgr) ListServices() ([]string, error) { return m.realMgr.ListServices() } +//MgProvider is an implementation of WinServiceManagerProvider interface returning WinSvcMgr type MgProvider struct { } @@ -76,6 +58,30 @@ func (rmr *MgProvider) Connect() (WinServiceManager, error) { } } +var sampleConfig = ` + ## Names of the services to monitor. Leave empty to monitor all the available services on the host + service_names = [ + "LanmanServer", + "TermService", + ] +` + +var description = "Input plugin to report Windows services info." + +//WinServices is an implementation if telegraf.Input interface, providing info about Windows Services +type WinServices struct { + ServiceNames []string `toml:"service_names"` + mgrProvider WinServiceManagerProvider +} + +type ServiceInfo struct { + ServiceName string + DisplayName string + State int + StartUpMode int + Error error +} + func (m *WinServices) Description() string { return description } From 547cb9bab9c2616d41a53e1e1699bb0286399dd5 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 2 Aug 2017 11:16:34 +0200 Subject: [PATCH 40/44] Support types made internal --- .../inputs/win_services/win_services_test.go | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plugins/inputs/win_services/win_services_test.go b/plugins/inputs/win_services/win_services_test.go index 49b6587c66ba8..73897b982ac6d 100644 --- a/plugins/inputs/win_services/win_services_test.go +++ b/plugins/inputs/win_services/win_services_test.go @@ -14,15 +14,16 @@ import ( "testing" ) -type TestData struct { - //collection that will be returned in ListServices if service arrays passed into WinServices constructor is empty +//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 + services []serviceTestInfo } -type ServiceTestInfo struct { +type serviceTestInfo struct { serviceOpenError error serviceQueryError error serviceConfigError error @@ -33,7 +34,7 @@ type ServiceTestInfo struct { } type FakeSvcMgr struct { - testData TestData + testData testData } func (m *FakeSvcMgr) Disconnect() error { @@ -62,7 +63,7 @@ func (m *FakeSvcMgr) ListServices() ([]string, error) { } type FakeMgProvider struct { - testData TestData + testData testData } func (m *FakeMgProvider) Connect() (WinServiceManager, error) { @@ -74,7 +75,7 @@ func (m *FakeMgProvider) Connect() (WinServiceManager, error) { } type FakeWinSvc struct { - testData ServiceTestInfo + testData serviceTestInfo } func (m *FakeWinSvc) Close() error { @@ -95,15 +96,15 @@ func (m *FakeWinSvc) Query() (svc.Status, error) { } } -var testErrors = []TestData{ +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{ + {[]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}, }}, - {nil, nil, nil, []ServiceTestInfo{ + {nil, nil, nil, []serviceTestInfo{ {errors.New("Fake srv open error"), nil, nil, "Fake service 1", "", 0, 0}, }}, } @@ -153,8 +154,8 @@ func TestServiceErrors(t *testing.T) { } -var testSimpleData = []TestData{ - {[]string{"Service 1", "Service 2"}, nil, nil, []ServiceTestInfo{ +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}, }}, From 86cef94d96db23583223f834d2e3001e2ab398f2 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Fri, 4 Aug 2017 22:49:59 +0200 Subject: [PATCH 41/44] Merged changes from vh-win-services branch from Godeps_windows --- Godeps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Godeps b/Godeps index 0fb97bc8afb08..48f9138e8a874 100644 --- a/Godeps +++ b/Godeps @@ -76,7 +76,7 @@ github.com/yuin/gopher-lua 66c871e454fcf10251c61bf8eff02d0978cae75a github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363 golang.org/x/crypto dc137beb6cce2043eb6b5f223ab8bf51c32459f4 golang.org/x/net f2499483f923065a842d38eb4c7f1927e6fc6e6d -golang.org/x/sys a646d33e2ee3172a661fc09bca23bb4889a41bc8 +golang.org/x/sys 739734461d1c916b6c72a63d7efda2b27edb369f golang.org/x/text 506f9d5c962f284575e88337e7d9296d27e729d3 gopkg.in/asn1-ber.v1 4e86f4367175e39f69d9358a5f17b4dda270378d gopkg.in/fatih/pool.v2 6e328e67893eb46323ad06f0e92cb9536babbabc From 215828e1c89fc826970238dfe797eb48a8f134c1 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Fri, 4 Aug 2017 22:57:04 +0200 Subject: [PATCH 42/44] Proper skipping of tests in short mode --- .../win_services_integration_test.go | 90 ++++++++++--------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/plugins/inputs/win_services/win_services_integration_test.go b/plugins/inputs/win_services/win_services_integration_test.go index 9753bc3508675..2017465145441 100644 --- a/plugins/inputs/win_services/win_services_integration_test.go +++ b/plugins/inputs/win_services/win_services_integration_test.go @@ -15,67 +15,71 @@ var InvalidServices = []string{"XYZ1@", "ZYZ@", "SDF_@#"} var KnownServices = []string{"LanmanServer", "TermService"} func TestList(t *testing.T) { - if !testing.Short() { - services, err := listServices(&MgProvider{}, KnownServices) - require.NoError(t, err) - assert.Len(t, services, 2, "Different number of services") - assert.Equal(t, services[0].ServiceName, KnownServices[0]) - assert.Nil(t, services[0].Error) - assert.Equal(t, services[1].ServiceName, KnownServices[1]) - assert.Nil(t, services[1].Error) + if testing.Short() { + t.Skip("Skipping integration test in short mode") } + services, err := listServices(&MgProvider{}, KnownServices) + require.NoError(t, err) + assert.Len(t, services, 2, "Different number of services") + assert.Equal(t, services[0].ServiceName, KnownServices[0]) + assert.Nil(t, services[0].Error) + assert.Equal(t, services[1].ServiceName, KnownServices[1]) + assert.Nil(t, services[1].Error) } func TestEmptyList(t *testing.T) { - if !testing.Short() { - services, err := listServices(&MgProvider{}, []string{}) - require.NoError(t, err) - assert.Condition(t, func() bool { return len(services) > 20 }, "Too few service") + if testing.Short() { + t.Skip("Skipping integration test in short mode") } + services, err := listServices(&MgProvider{}, []string{}) + require.NoError(t, err) + assert.Condition(t, func() bool { return len(services) > 20 }, "Too few service") } func TestListEr(t *testing.T) { - if !testing.Short() { - services, err := listServices(&MgProvider{}, InvalidServices) - require.NoError(t, err) - assert.Len(t, services, 3, "Different number of services") - for i := 0; i < 3; i++ { - assert.Equal(t, services[i].ServiceName, InvalidServices[i]) - assert.NotNil(t, services[i].Error) - } + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + services, err := listServices(&MgProvider{}, InvalidServices) + require.NoError(t, err) + assert.Len(t, services, 3, "Different number of services") + for i := 0; i < 3; i++ { + assert.Equal(t, services[i].ServiceName, InvalidServices[i]) + assert.NotNil(t, services[i].Error) } } func TestGather(t *testing.T) { - if !testing.Short() { - ws := &WinServices{KnownServices, &MgProvider{}} - assert.Len(t, ws.ServiceNames, 2, "Different number of services") - var acc testutil.Accumulator - require.NoError(t, ws.Gather(&acc)) - assert.Len(t, acc.Errors, 0, "There should be no errors after gather") - - for i := 0; i < 2; i++ { - fields := make(map[string]interface{}) - tags := make(map[string]string) - si := getServiceInfo(KnownServices[i]) - fields["state"] = int(si.State) - fields["startup_mode"] = int(si.StartUpMode) - tags["service_name"] = si.ServiceName - tags["display_name"] = si.DisplayName - acc.AssertContainsTaggedFields(t, "win_services", fields, tags) - } + if testing.Short() { + t.Skip("Skipping integration test in short mode") } + ws := &WinServices{KnownServices, &MgProvider{}} + assert.Len(t, ws.ServiceNames, 2, "Different number of services") + var acc testutil.Accumulator + require.NoError(t, ws.Gather(&acc)) + assert.Len(t, acc.Errors, 0, "There should be no errors after gather") + for i := 0; i < 2; i++ { + fields := make(map[string]interface{}) + tags := make(map[string]string) + si := getServiceInfo(KnownServices[i]) + fields["state"] = int(si.State) + fields["startup_mode"] = int(si.StartUpMode) + tags["service_name"] = si.ServiceName + tags["display_name"] = si.DisplayName + acc.AssertContainsTaggedFields(t, "win_services", fields, tags) + } } func TestGatherErrors(t *testing.T) { - if !testing.Short() { - ws := &WinServices{InvalidServices, &MgProvider{}} - assert.Len(t, ws.ServiceNames, 3, "Different number of services") - var acc testutil.Accumulator - require.NoError(t, ws.Gather(&acc)) - assert.Len(t, acc.Errors, 3, "There should be 3 errors after gather") + if testing.Short() { + t.Skip("Skipping integration test in short mode") } + ws := &WinServices{InvalidServices, &MgProvider{}} + assert.Len(t, ws.ServiceNames, 3, "Different number of services") + var acc testutil.Accumulator + require.NoError(t, ws.Gather(&acc)) + assert.Len(t, acc.Errors, 3, "There should be 3 errors after gather") } func getServiceInfo(srvName string) *ServiceInfo { From d30749375e67ff24089d368ec4e4280a5cd8fa20 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Mon, 7 Aug 2017 22:59:10 +0200 Subject: [PATCH 43/44] Unit test don't need admin permission --- plugins/inputs/win_services/win_services_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/inputs/win_services/win_services_test.go b/plugins/inputs/win_services/win_services_test.go index 73897b982ac6d..3c05e85c59447 100644 --- a/plugins/inputs/win_services/win_services_test.go +++ b/plugins/inputs/win_services/win_services_test.go @@ -1,6 +1,5 @@ // +build windows -//this test must be run under administrator account package win_services import ( From 2b1ee6d65b09db2204510a4bbd3f7c55c599c529 Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Mon, 7 Aug 2017 23:00:08 +0200 Subject: [PATCH 44/44] Win_services folder added to windows tests --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 6e9a2b28bb67f..9e92de04e58e2 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ test: test-windows: go test ./plugins/inputs/ping/... go test ./plugins/inputs/win_perf_counters/... + go test ./plugins/inputs/win_services/... lint: go vet ./...