diff --git a/README.md b/README.md index 7db24b7..6e38d5a 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,18 @@ Flags: --smartctl.interval=60s The interval between smarctl polls --smartctl.device=SMARTCTL.DEVICE ... The device to monitor (repeatable) - --web.listen-address=":9633" - Address to listen on for web interface and telemetry + --smartctl.device-exclude="" + Regexp of devices to exclude from automatic scanning. (mutually exclusive to + device-include) + --smartctl.device-include="" + Regexp of devices to exclude from automatic scanning. (mutually exclusive to + device-exclude) --web.telemetry-path="/metrics" Path under which to expose metrics + --web.systemd-socket Use systemd socket activation listeners instead of port listeners (Linux only). + --web.listen-address=:9633 ... + Addresses on which to expose metrics and web interface. Repeatable for multiple + addresses. --web.config.file="" [EXPERIMENTAL] Path to configuration file that can enable TLS or authentication. --log.level=info Only log messages with the given severity or above. One of: [debug, info, warn, error] diff --git a/device_filter.go b/device_filter.go new file mode 100644 index 0000000..4bbcecd --- /dev/null +++ b/device_filter.go @@ -0,0 +1,41 @@ +// Copyright 2022 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "regexp" +) + +type deviceFilter struct { + ignorePattern *regexp.Regexp + acceptPattern *regexp.Regexp +} + +func newDeviceFilter(ignoredPattern, acceptPattern string) (f deviceFilter) { + if ignoredPattern != "" { + f.ignorePattern = regexp.MustCompile(ignoredPattern) + } + + if acceptPattern != "" { + f.acceptPattern = regexp.MustCompile(acceptPattern) + } + + return +} + +// ignored returns whether the device should be ignored +func (f *deviceFilter) ignored(name string) bool { + return ((f.ignorePattern != nil && f.ignorePattern.MatchString(name)) || + (f.acceptPattern != nil && !f.acceptPattern.MatchString(name))) +} diff --git a/device_filter_test.go b/device_filter_test.go new file mode 100644 index 0000000..74a3e30 --- /dev/null +++ b/device_filter_test.go @@ -0,0 +1,43 @@ +// Copyright 2022 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "testing" +) + +func TestDeviceFilter(t *testing.T) { + tests := []struct { + ignore string + accept string + name string + expectedResult bool + }{ + {"", "", "eth0", false}, + {"", "^💩0$", "💩0", false}, + {"", "^💩0$", "💩1", true}, + {"", "^💩0$", "veth0", true}, + {"^💩", "", "💩3", true}, + {"^💩", "", "veth0", false}, + } + + for _, test := range tests { + filter := newDeviceFilter(test.ignore, test.accept) + result := filter.ignored(test.name) + + if result != test.expectedResult { + t.Errorf("ignorePattern=%v acceptPattern=%v ifname=%v expected=%v result=%v", test.ignore, test.accept, test.name, test.expectedResult, result) + } + } +} diff --git a/main.go b/main.go index d1fde84..3d4e5ba 100644 --- a/main.go +++ b/main.go @@ -69,11 +69,38 @@ var ( smartctlDevices = kingpin.Flag("smartctl.device", "The device to monitor (repeatable)", ).Strings() + smartctlDeviceExclude = kingpin.Flag( + "smartctl.device-exclude", + "Regexp of devices to exclude from automatic scanning. (mutually exclusive to device-include)", + ).Default("").String() + smartctlDeviceInclude = kingpin.Flag( + "smartctl.device-include", + "Regexp of devices to exclude from automatic scanning. (mutually exclusive to device-exclude)", + ).Default("").String() smartctlFakeData = kingpin.Flag("smartctl.fake-data", "The device to monitor (repeatable)", ).Default("false").Hidden().Bool() ) +// scanDevices uses smartctl to gather the list of available devices. +func scanDevices(logger log.Logger) []string { + filter := newDeviceFilter(*smartctlDeviceExclude, *smartctlDeviceInclude) + + json := readSMARTctlDevices(logger) + scanDevices := json.Get("devices").Array() + var scanDeviceResult []string + for _, d := range scanDevices { + deviceName := d.Get("name").String() + if filter.ignored(deviceName) { + level.Info(logger).Log("msg", "Ignoring device", "name", deviceName) + } else { + level.Info(logger).Log("msg", "Found device", "name", deviceName) + scanDeviceResult = append(scanDeviceResult, deviceName) + } + } + return scanDeviceResult +} + func main() { metricsPath := kingpin.Flag( "web.telemetry-path", "Path under which to expose metrics", @@ -90,34 +117,12 @@ func main() { level.Info(logger).Log("msg", "Starting smartctl_exporter", "version", version.Info()) level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext()) - // Scan the host devices - json := readSMARTctlDevices(logger) - scanDevices := json.Get("devices").Array() - scanDevicesSet := make(map[string]bool) - var scanDeviceNames []string - for _, d := range scanDevices { - deviceName := d.Get("name").String() - level.Debug(logger).Log("msg", "Found device", "name", deviceName) - scanDevicesSet[deviceName] = true - scanDeviceNames = append(scanDeviceNames, deviceName) - } - - // Read the configuration and verify that it is available - devices := *smartctlDevices - var readDeviceNames []string - for _, device := range devices { - if _, ok := scanDevicesSet[device]; ok { - readDeviceNames = append(readDeviceNames, device) - } else { - level.Warn(logger).Log("msg", "Device unavailable", "name", device) - } - } - - if len(readDeviceNames) > 0 { - devices = readDeviceNames + var devices []string + if len(*smartctlDevices) > 0 { + devices = *smartctlDevices } else { level.Info(logger).Log("msg", "No devices specified, trying to load them automatically") - devices = scanDeviceNames + devices = scanDevices(logger) } if len(devices) == 0 {