Merge pull request #99 from prometheus-community/superq/filter

Add device filtering
This commit is contained in:
Ben Kochie 2022-12-09 22:04:04 +01:00 committed by GitHub
commit 2df95c8071
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 125 additions and 28 deletions

View file

@ -28,10 +28,18 @@ Flags:
--smartctl.interval=60s The interval between smarctl polls --smartctl.interval=60s The interval between smarctl polls
--smartctl.device=SMARTCTL.DEVICE ... --smartctl.device=SMARTCTL.DEVICE ...
The device to monitor (repeatable) The device to monitor (repeatable)
--web.listen-address=":9633" --smartctl.device-exclude=""
Address to listen on for web interface and telemetry 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" --web.telemetry-path="/metrics"
Path under which to expose 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. --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, --log.level=info Only log messages with the given severity or above. One of: [debug, info, warn,
error] error]

41
device_filter.go Normal file
View file

@ -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)))
}

43
device_filter_test.go Normal file
View file

@ -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)
}
}
}

57
main.go
View file

@ -69,11 +69,38 @@ var (
smartctlDevices = kingpin.Flag("smartctl.device", smartctlDevices = kingpin.Flag("smartctl.device",
"The device to monitor (repeatable)", "The device to monitor (repeatable)",
).Strings() ).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", smartctlFakeData = kingpin.Flag("smartctl.fake-data",
"The device to monitor (repeatable)", "The device to monitor (repeatable)",
).Default("false").Hidden().Bool() ).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() { func main() {
metricsPath := kingpin.Flag( metricsPath := kingpin.Flag(
"web.telemetry-path", "Path under which to expose metrics", "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", "Starting smartctl_exporter", "version", version.Info())
level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext()) level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext())
// Scan the host devices var devices []string
json := readSMARTctlDevices(logger) if len(*smartctlDevices) > 0 {
scanDevices := json.Get("devices").Array() devices = *smartctlDevices
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
} else { } else {
level.Info(logger).Log("msg", "No devices specified, trying to load them automatically") level.Info(logger).Log("msg", "No devices specified, trying to load them automatically")
devices = scanDeviceNames devices = scanDevices(logger)
} }
if len(devices) == 0 { if len(devices) == 0 {