Skip to content

Commit

Permalink
Add device filtering
Browse files Browse the repository at this point in the history
* Add device include/exclude filters for the automatic scanning.
* Refactor scanning to not scan if devices are manually specified.
* Don't try and filter manually specified devices.

Signed-off-by: SuperQ <superq@gmail.com>
  • Loading branch information
SuperQ committed Dec 9, 2022
1 parent a58c632 commit fff5f67
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 28 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
41 changes: 41 additions & 0 deletions device_filter.go
Original file line number Diff line number Diff line change
@@ -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 changes: 43 additions & 0 deletions device_filter_test.go
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 26 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 {
Expand Down

0 comments on commit fff5f67

Please sign in to comment.