From 7814adf42536377701ba7a91e5dcb86b747ab2d1 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Mon, 9 Mar 2020 07:19:27 -0700 Subject: [PATCH 1/9] major refactor to support different systemd version --- .../module/system/_meta/config.reference.yml | 5 +- .../module/system/service/_meta/docs.asciidoc | 1 + metricbeat/module/system/service/dbus.go | 164 ++++++++++++++++++ metricbeat/module/system/service/service.go | 17 +- 4 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 metricbeat/module/system/service/dbus.go diff --git a/metricbeat/module/system/_meta/config.reference.yml b/metricbeat/module/system/_meta/config.reference.yml index a6023d23ddc..2415126898c 100644 --- a/metricbeat/module/system/_meta/config.reference.yml +++ b/metricbeat/module/system/_meta/config.reference.yml @@ -74,4 +74,7 @@ #diskio.include_devices: [] # Filter systemd services by status or sub-status - #service.state_filter: [] + #service.state_filter: ["active"] + + # Filter systemd services based on a name pattern + #service.pattern_filter: ["ssh*", "nfs*"] \ No newline at end of file diff --git a/metricbeat/module/system/service/_meta/docs.asciidoc b/metricbeat/module/system/service/_meta/docs.asciidoc index 1cb512dc7cd..8f7bdadebcd 100644 --- a/metricbeat/module/system/service/_meta/docs.asciidoc +++ b/metricbeat/module/system/service/_meta/docs.asciidoc @@ -14,6 +14,7 @@ For more information, https://www.freedesktop.org/software/systemd/man/systemd.r === Configuration *`service.state_filter`* - A list of service states to filter by. This can be any of the states or sub-states known to systemd. +*`service.pattern_filter`* - A list of glob patterns to filter service names by. This is an "or" filter, and will report any systemd unit that matches at least one filter pattern. [float] === Dashboard diff --git a/metricbeat/module/system/service/dbus.go b/metricbeat/module/system/service/dbus.go new file mode 100644 index 00000000000..774a38bbdc1 --- /dev/null +++ b/metricbeat/module/system/service/dbus.go @@ -0,0 +1,164 @@ +package service + +import ( + "encoding/xml" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/coreos/go-systemd/v22/dbus" + dbusRaw "github.com/godbus/dbus" + "github.com/pkg/errors" +) + +type unitFetcher func(conn *dbus.Conn, states, patterns []string) ([]dbus.UnitStatus, error) + +// instrospectForUnitMethods determines what methods are available via dbus for listing systemd units. +// We have a number of functions, some better than others, for getting and filtering unit lists. +// This will attempt to find the most optimal method, and move down to methods that require more work. +func instrospectForUnitMethods() (unitFetcher, error) { + //setup a dbus connection + conn, err := dbusRaw.SystemBusPrivate() + if err != nil { + return nil, errors.Wrap(err, "error getting connection to system bus") + } + + auth := dbusRaw.AuthExternal(strconv.Itoa(os.Getuid())) + err = conn.Auth([]dbusRaw.Auth{auth}) + if err != nil { + return nil, errors.Wrap(err, "error authenticating") + } + + err = conn.Hello() + if err != nil { + return nil, errors.Wrap(err, "error in Hello") + } + + var props string + + //call "introspect" on the systemd1 path to see what ListUnit* methods are available + obj := conn.Object("org.freedesktop.systemd1", dbusRaw.ObjectPath("/org/freedesktop/systemd1")) + err = obj.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&props) + if err != nil { + return nil, errors.Wrap(err, "error calling dbus") + } + + unitMap, err := parseXMLAndReturnMethods(props) + if err != nil { + return nil, errors.Wrap(err, "error handling XML") + } + + //return a function callback ordered by desirability + if _, ok := unitMap["ListUnitsByPatterns"]; ok { + return listUnitsByPatternWrapper, nil + } else if _, ok := unitMap["ListUnitsFiltered"]; ok { + return listUnitsFilteredWrapper, nil + } else if _, ok := unitMap["ListUnits"]; ok { + return listUnitsWrapper, nil + } + return nil, fmt.Errorf("No supported list Units function: %v", unitMap) + +} + +func parseXMLAndReturnMethods(str string) (map[string]bool, error) { + + type Method struct { + Name string `xml:"name,attr"` + } + + type Iface struct { + Name string `xml:"name,attr"` + Method []Method `xml:"method"` + } + + type IntrospectData struct { + XMLName xml.Name `xml:"node"` + Interface []Iface `xml:"interface"` + } + + methods := IntrospectData{} + + err := xml.Unmarshal([]byte(str), &methods) + if err != nil { + return nil, errors.Wrap(err, "error unmarshalling XML") + } + + if len(methods.Interface) == 0 { + return nil, errors.Wrap(err, "no methods found on introspect") + } + methodMap := make(map[string]bool) + for _, iface := range methods.Interface { + for _, method := range iface.Method { + if strings.Contains(method.Name, "ListUnits") { + methodMap[method.Name] = true + } + } + } + + return methodMap, nil +} + +// listUnitsByPatternWrapper is a bare wrapper for the unitFetcher type +func listUnitsByPatternWrapper(conn *dbus.Conn, states, patterns []string) ([]dbus.UnitStatus, error) { + return conn.ListUnitsByPatterns(states, patterns) +} + +//listUnitsFilteredWrapper wraps the dbus ListUnitsFiltered method +func listUnitsFilteredWrapper(conn *dbus.Conn, states, patterns []string) ([]dbus.UnitStatus, error) { + units, err := conn.ListUnitsFiltered(states) + if err != nil { + return nil, errors.Wrap(err, "ListUnitsFiltered error") + } + + return matchUnitPatterns(patterns, units) +} + +// listUnitsWrapper wraps the dbus ListUnits method +func listUnitsWrapper(conn *dbus.Conn, states, patterns []string) ([]dbus.UnitStatus, error) { + units, err := conn.ListUnits() + if err != nil { + return nil, errors.Wrap(err, "ListUnits error") + } + if len(patterns) > 0 { + units, err = matchUnitPatterns(patterns, units) + if err != nil { + return nil, errors.Wrap(err, "error matching unit patterns") + } + } + + if len(states) > 0 { + var finalUnits []dbus.UnitStatus + for _, unit := range units { + for _, state := range states { + if unit.LoadState == state || unit.ActiveState == state || unit.SubState == state { + finalUnits = append(finalUnits, unit) + break + } + } + } + return finalUnits, nil + } + + return units, nil +} + +// matchUnitPatterns returns a list of units that match the pattern list. +// This algo, including filepath.Match, is designed to (somewhat) emulate the behavior of ListUnitsByPatterns, which uses `fnmatch`. +func matchUnitPatterns(patterns []string, units []dbus.UnitStatus) ([]dbus.UnitStatus, error) { + var matchUnits []dbus.UnitStatus + for _, unit := range units { + for _, pattern := range patterns { + match, err := filepath.Match(pattern, unit.Name) + if err != nil { + return nil, errors.Wrapf(err, "error matching with pattern %s", pattern) + } + if match { + matchUnits = append(matchUnits, unit) + break + } + } + } + return matchUnits, nil +} diff --git a/metricbeat/module/system/service/service.go b/metricbeat/module/system/service/service.go index d2985941894..d969726a681 100644 --- a/metricbeat/module/system/service/service.go +++ b/metricbeat/module/system/service/service.go @@ -30,7 +30,8 @@ import ( // Config stores the config object type Config struct { - StateFilter []string `config:"service.state_filter"` + StateFilter []string `config:"service.state_filter"` + PatternFilter []string `config:"service.pattern_filter"` } // init registers the MetricSet with the central registry as soon as the program @@ -47,8 +48,9 @@ func init() { // interface methods except for Fetch. type MetricSet struct { mb.BaseMetricSet - conn *dbus.Conn - cfg Config + conn *dbus.Conn + cfg Config + unitList unitFetcher } // New creates a new instance of the MetricSet. New is responsible for unpacking @@ -66,10 +68,16 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return nil, errors.Wrap(err, "error connecting to dbus") } + unitFunction, err := instrospectForUnitMethods() + if err != nil { + return nil, errors.Wrap(err, "error finding ListUnits Method") + } + return &MetricSet{ BaseMetricSet: base, conn: conn, cfg: config, + unitList: unitFunction, }, nil } @@ -77,7 +85,8 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { // format. It publishes the event which is then forwarded to the output. In case // of an error set the Error field of mb.Event or simply call report.Error(). func (m *MetricSet) Fetch(report mb.ReporterV2) error { - units, err := m.conn.ListUnitsByPatterns(m.cfg.StateFilter, []string{"*.service"}) + + units, err := m.unitList(m.conn, m.cfg.StateFilter, append([]string{"*.service"}, m.cfg.PatternFilter...)) if err != nil { return errors.Wrap(err, "error getting list of running units") } From f97a6c5733ad10f17a09315f53a20726432efa32 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Mon, 9 Mar 2020 07:31:46 -0700 Subject: [PATCH 2/9] format and updates --- metricbeat/docs/modules/system.asciidoc | 6 ++++-- metricbeat/module/system/service/dbus.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/metricbeat/docs/modules/system.asciidoc b/metricbeat/docs/modules/system.asciidoc index 1c7b49716af..66dca069b70 100644 --- a/metricbeat/docs/modules/system.asciidoc +++ b/metricbeat/docs/modules/system.asciidoc @@ -232,8 +232,10 @@ metricbeat.modules: #diskio.include_devices: [] # Filter systemd services by status or sub-status - #service.state_filter: [] ----- + #service.state_filter: ["active"] + + # Filter systemd services based on a name pattern + #service.pattern_filter: ["ssh*", "nfs*"]---- [float] === Metricsets diff --git a/metricbeat/module/system/service/dbus.go b/metricbeat/module/system/service/dbus.go index 774a38bbdc1..67046a1ce79 100644 --- a/metricbeat/module/system/service/dbus.go +++ b/metricbeat/module/system/service/dbus.go @@ -1,3 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + package service import ( From 2b6a45761371eb0dba3162be7dc382b409dab7fb Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Mon, 9 Mar 2020 08:00:48 -0700 Subject: [PATCH 3/9] update ref docs --- metricbeat/metricbeat.reference.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/metricbeat/metricbeat.reference.yml b/metricbeat/metricbeat.reference.yml index 7146db1cd66..56a1fef5083 100644 --- a/metricbeat/metricbeat.reference.yml +++ b/metricbeat/metricbeat.reference.yml @@ -133,8 +133,10 @@ metricbeat.modules: #diskio.include_devices: [] # Filter systemd services by status or sub-status - #service.state_filter: [] + #service.state_filter: ["active"] + # Filter systemd services based on a name pattern + #service.pattern_filter: ["ssh*", "nfs*"] #------------------------------ Aerospike Module ------------------------------ - module: aerospike metricsets: ["namespace"] From a2990bcd29b2c0a6b7651f5b7ccdaa5746b14e2c Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Mon, 9 Mar 2020 08:41:41 -0700 Subject: [PATCH 4/9] update ref, again --- x-pack/metricbeat/metricbeat.reference.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index 00b4daa5613..a69661cd99a 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -133,8 +133,10 @@ metricbeat.modules: #diskio.include_devices: [] # Filter systemd services by status or sub-status - #service.state_filter: [] + #service.state_filter: ["active"] + # Filter systemd services based on a name pattern + #service.pattern_filter: ["ssh*", "nfs*"] #------------------------------- Activemq Module ------------------------------- - module: activemq metricsets: ['broker', 'queue', 'topic'] From 4bedeb1e0dcb46bb7f30058cb728ba7e6c38df22 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 10 Mar 2020 09:05:26 -0700 Subject: [PATCH 5/9] add newline --- metricbeat/docs/modules/system.asciidoc | 3 ++- metricbeat/metricbeat.reference.yml | 1 + metricbeat/module/system/_meta/config.reference.yml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/metricbeat/docs/modules/system.asciidoc b/metricbeat/docs/modules/system.asciidoc index 66dca069b70..722f24c616e 100644 --- a/metricbeat/docs/modules/system.asciidoc +++ b/metricbeat/docs/modules/system.asciidoc @@ -235,7 +235,8 @@ metricbeat.modules: #service.state_filter: ["active"] # Filter systemd services based on a name pattern - #service.pattern_filter: ["ssh*", "nfs*"]---- + #service.pattern_filter: ["ssh*", "nfs*"] +---- [float] === Metricsets diff --git a/metricbeat/metricbeat.reference.yml b/metricbeat/metricbeat.reference.yml index 56a1fef5083..169fe78561a 100644 --- a/metricbeat/metricbeat.reference.yml +++ b/metricbeat/metricbeat.reference.yml @@ -137,6 +137,7 @@ metricbeat.modules: # Filter systemd services based on a name pattern #service.pattern_filter: ["ssh*", "nfs*"] + #------------------------------ Aerospike Module ------------------------------ - module: aerospike metricsets: ["namespace"] diff --git a/metricbeat/module/system/_meta/config.reference.yml b/metricbeat/module/system/_meta/config.reference.yml index 2415126898c..39438686dbd 100644 --- a/metricbeat/module/system/_meta/config.reference.yml +++ b/metricbeat/module/system/_meta/config.reference.yml @@ -77,4 +77,4 @@ #service.state_filter: ["active"] # Filter systemd services based on a name pattern - #service.pattern_filter: ["ssh*", "nfs*"] \ No newline at end of file + #service.pattern_filter: ["ssh*", "nfs*"] From 1178054b40ec25333c0abde32debf6e5e116986f Mon Sep 17 00:00:00 2001 From: Alex K <8418476+fearful-symmetry@users.noreply.github.com> Date: Tue, 10 Mar 2020 12:13:39 -0700 Subject: [PATCH 6/9] Fix error string Co-Authored-By: Jaime Soriano Pastor --- metricbeat/module/system/service/dbus.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/metricbeat/module/system/service/dbus.go b/metricbeat/module/system/service/dbus.go index 67046a1ce79..01a53df1215 100644 --- a/metricbeat/module/system/service/dbus.go +++ b/metricbeat/module/system/service/dbus.go @@ -75,8 +75,7 @@ func instrospectForUnitMethods() (unitFetcher, error) { } else if _, ok := unitMap["ListUnits"]; ok { return listUnitsWrapper, nil } - return nil, fmt.Errorf("No supported list Units function: %v", unitMap) - + return nil, fmt.Errorf("no supported list Units function: %v", unitMap) } func parseXMLAndReturnMethods(str string) (map[string]bool, error) { From 48ef07c621915222e7d96899b836708b9bddfe9e Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 10 Mar 2020 12:52:25 -0700 Subject: [PATCH 7/9] add changelog entry --- CHANGELOG.next.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 7182cb17c9a..1b9404f26ea 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -112,6 +112,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix detection and logging of some error cases with light modules. {pull}14706[14706] - Fix imports after PR was merged before rebase. {pull}16756[16756] - Add dashboard for `redisenterprise` module. {pull}16752[16752] +- Dynamically choose a method for the system/service metricset to support older linux distros. {pull}16902[16902] *Packetbeat* From 5461eec1ab8c13ceb2cf154bb924ed07956fd4e9 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Wed, 11 Mar 2020 08:22:40 -0700 Subject: [PATCH 8/9] make update --- x-pack/metricbeat/metricbeat.reference.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index a69661cd99a..1e4163519cf 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -137,6 +137,7 @@ metricbeat.modules: # Filter systemd services based on a name pattern #service.pattern_filter: ["ssh*", "nfs*"] + #------------------------------- Activemq Module ------------------------------- - module: activemq metricsets: ['broker', 'queue', 'topic'] From d1d69dfecab55a066fd3f355390bafc9026cb0fc Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Wed, 11 Mar 2020 12:36:04 -0700 Subject: [PATCH 9/9] add build target --- metricbeat/module/system/service/dbus.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/metricbeat/module/system/service/dbus.go b/metricbeat/module/system/service/dbus.go index 01a53df1215..62112922136 100644 --- a/metricbeat/module/system/service/dbus.go +++ b/metricbeat/module/system/service/dbus.go @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +//+build !netbsd + package service import (