Skip to content

Commit

Permalink
feat(enhancement): Allow excluding datasets by regular expression
Browse files Browse the repository at this point in the history
Closes #3
  • Loading branch information
pdf committed Jun 22, 2021
1 parent 056b386 commit 8dd48ba
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 40 deletions.
41 changes: 17 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,28 @@ go get -u github.com/pdf/zfs_exporter
usage: zfs_exporter [<flags>]
Flags:
-h, --help Show context-sensitive help (also try --help-long and
--help-man).
-h, --help Show context-sensitive help (also try --help-long and --help-man).
--collector.dataset-filesystem
Enable the dataset-filesystem collector (default:
enabled)
Enable the dataset-filesystem collector (default: enabled)
--collector.dataset-snapshot
Enable the dataset-snapshot collector (default:
disabled)
Enable the dataset-snapshot collector (default: disabled)
--collector.dataset-volume
Enable the dataset-volume collector (default: enabled)
--collector.pool Enable the pool collector (default: enabled)
Enable the dataset-volume collector (default: enabled)
--collector.pool Enable the pool collector (default: enabled)
--web.listen-address=":9134"
Address on which to expose metrics and web interface.
Address on which to expose metrics and web interface.
--web.telemetry-path="/metrics"
Path under which to expose metrics.
--deadline=8s Maximum duration that a collection should run before
returning cached data. Should be set to a value
shorter than your scrape timeout duration. The current
collection run will continue and update the cache when
complete (default: 8s)
--pool=POOL ... Name of the pool(s) to collect, repeat for multiple
pools (default: all pools).
--log.level="info" Only log messages with the given severity or above.
Valid levels: [debug, info, warn, error, fatal]
--log.format="logger:stderr"
Set the log target and format. Example:
"logger:syslog?appname=bob&local=7" or
"logger:stdout?json=true"
--version Show application version.
Path under which to expose metrics.
--deadline=8s Maximum duration that a collection should run before returning cached data. Should
be set to a value shorter than your scrape timeout duration. The current
collection run will continue and update the cache when complete (default: 8s)
--pool=POOL ... Name of the pool(s) to collect, repeat for multiple pools (default: all pools).
--exclude=EXCLUDE ... Exclude datasets/snapshots/volumes that match the provided regex (e.g.
'^rpool/docker/'), may be specified multiple times.
--log.level=info Only log messages with the given severity or above. One of: [debug, info, warn,
error]
--log.format=logfmt Output format of log messages. One of: [logfmt, json]
--version Show application version.
```

Collectors that are enabled by default can be negated by prefixing the flag with `--no-*`, ie:
Expand Down
2 changes: 1 addition & 1 deletion collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type State struct {
}

type Collector interface {
update(ch chan<- metric, pools []*zfs.Zpool) error
update(ch chan<- metric, pools []*zfs.Zpool, excludes regexpCollection) error
}

type metric struct {
Expand Down
9 changes: 6 additions & 3 deletions collector/dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ type datasetCollector struct {
referencedBytes desc
}

func (c *datasetCollector) update(ch chan<- metric, pools []*zfs.Zpool) error {
func (c *datasetCollector) update(ch chan<- metric, pools []*zfs.Zpool, excludes regexpCollection) error {
for _, pool := range pools {
if err := c.updatePoolMetrics(ch, pool); err != nil {
if err := c.updatePoolMetrics(ch, pool, excludes); err != nil {
return err
}
}

return nil
}

func (c *datasetCollector) updatePoolMetrics(ch chan<- metric, pool *zfs.Zpool) error {
func (c *datasetCollector) updatePoolMetrics(ch chan<- metric, pool *zfs.Zpool, excludes regexpCollection) error {
var (
datasets []*zfs.Dataset
err error
Expand All @@ -53,6 +53,9 @@ func (c *datasetCollector) updatePoolMetrics(ch chan<- metric, pool *zfs.Zpool)
}

for _, dataset := range datasets {
if excludes.MatchString(dataset.Name) {
continue
}
if err = c.updateDatasetMetrics(ch, pool, dataset); err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion collector/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type poolCollector struct {
dedupRatio desc
}

func (c *poolCollector) update(ch chan<- metric, pools []*zfs.Zpool) error {
func (c *poolCollector) update(ch chan<- metric, pools []*zfs.Zpool, excludes regexpCollection) error {
for _, pool := range pools {
if err := c.updatePoolMetrics(ch, pool); err != nil {
return err
Expand Down
44 changes: 33 additions & 11 deletions collector/zfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package collector

import (
"context"
"regexp"
"sort"
"sync"
"time"
Expand All @@ -12,19 +13,35 @@ import (
"github.com/prometheus/client_golang/prometheus"
)

type regexpCollection []*regexp.Regexp

func (c regexpCollection) MatchString(input string) bool {
for _, r := range c {
if r.MatchString(input) {
return true
}
}

return false
}

// ZFSConfig configures a ZFS collector
type ZFSConfig struct {
Deadline time.Duration
Pools []string
Excludes []string
Logger log.Logger
}

// ZFS collector
type ZFS struct {
Pools []string
Collectors map[string]State
deadline time.Duration
cache *metricCache
ready chan struct{}
logger log.Logger
excludes regexpCollection
}

// Describe implements the prometheus.Collector interface.
Expand Down Expand Up @@ -56,16 +73,14 @@ func (c *ZFS) Collect(ch chan<- prometheus.Metric) {

// Upon exceeding deadline, send cached data for any metrics that have not already been reported.
go func() {
select {
case <-ctx.Done():
if err := ctx.Err(); err != nil && err != context.Canceled {
timeoutMutex.Lock()
c.cache.merge(cache)
cacheIndex := cache.index()
c.sendCached(ch, cacheIndex)
close(timeout) // assert timeout for flow control in other goroutines
timeoutMutex.Unlock()
}
<-ctx.Done()
if err := ctx.Err(); err != nil && err != context.Canceled {
timeoutMutex.Lock()
c.cache.merge(cache)
cacheIndex := cache.index()
c.sendCached(ch, cacheIndex)
close(timeout) // assert timeout for flow control in other goroutines
timeoutMutex.Unlock()
}
}()

Expand Down Expand Up @@ -164,7 +179,7 @@ func (c *ZFS) getPools(pools []string) ([]*zfs.Zpool, error) {

func (c *ZFS) execute(ctx context.Context, name string, collector Collector, ch chan<- metric, pools []*zfs.Zpool) {
begin := time.Now()
err := collector.update(ch, pools)
err := collector.update(ch, pools, c.excludes)
duration := time.Since(begin)
var success float64

Expand Down Expand Up @@ -196,14 +211,21 @@ func (c *ZFS) execute(ctx context.Context, name string, collector Collector, ch
}
}

// NewZFS instantiates a ZFS collector with the provided ZFSConfig
func NewZFS(config ZFSConfig) (*ZFS, error) {
sort.Strings(config.Pools)
sort.Strings(config.Excludes)
excludes := make(regexpCollection, len(config.Excludes))
for i, v := range config.Excludes {
excludes[i] = regexp.MustCompile(v)
}
ready := make(chan struct{}, 1)
ready <- struct{}{}
return &ZFS{
deadline: config.Deadline,
Pools: config.Pools,
Collectors: collectorStates,
excludes: excludes,
cache: newMetricCache(),
ready: ready,
logger: config.Logger,
Expand Down
2 changes: 2 additions & 0 deletions zfs_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func main() {
metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String()
deadline = kingpin.Flag("deadline", "Maximum duration that a collection should run before returning cached data. Should be set to a value shorter than your scrape timeout duration. The current collection run will continue and update the cache when complete (default: 8s)").Default("8s").Duration()
pools = kingpin.Flag("pool", "Name of the pool(s) to collect, repeat for multiple pools (default: all pools).").Strings()
excludes = kingpin.Flag("exclude", "Exclude datasets/snapshots/volumes that match the provided regex (e.g. '^rpool/docker/'), may be specified multiple times.").Strings()
)

promlogConfig := &promlog.Config{}
Expand All @@ -37,6 +38,7 @@ func main() {
c, err := collector.NewZFS(collector.ZFSConfig{
Deadline: *deadline,
Pools: *pools,
Excludes: *excludes,
Logger: logger,
})
if err != nil {
Expand Down

0 comments on commit 8dd48ba

Please sign in to comment.