Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filestat input plugin #1072

Merged
merged 1 commit into from
Apr 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ based on _prefix_ in addition to globs. This means that a filter like
- [#1056](https://github.com/influxdata/telegraf/pull/1056): Don't allow inputs to overwrite host tags.
- [#1035](https://github.com/influxdata/telegraf/issues/1035): Add `user`, `exe`, `pidfile` tags to procstat plugin.
- [#1041](https://github.com/influxdata/telegraf/issues/1041): Add `n_cpus` field to the system plugin.
- [#1072](https://github.com/influxdata/telegraf/pull/1072): New Input Plugin: filestat.

### Bugfixes

Expand Down
10 changes: 10 additions & 0 deletions etc/telegraf.conf
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,16 @@
# data_format = "influx"


# # Read stats about given file(s)
# [[inputs.filestat]]
# ## Files to gather stats about.
# ## These accept standard unix glob matching rules, but with the addition of
# ## ** as a "super asterisk". See https://github.com/gobwas/glob.
# ["/etc/telegraf/telegraf.conf", "/var/log/**.log"]
# ## If true, read the entire file and calculate an md5 checksum.
# md5 = false


# # Read metrics of haproxy, via socket or csv stats page
# [[inputs.haproxy]]
# ## An array of address to gather stats about. Specify an ip on hostname
Expand Down
1 change: 1 addition & 0 deletions plugins/inputs/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/dovecot"
_ "github.com/influxdata/telegraf/plugins/inputs/elasticsearch"
_ "github.com/influxdata/telegraf/plugins/inputs/exec"
_ "github.com/influxdata/telegraf/plugins/inputs/filestat"
_ "github.com/influxdata/telegraf/plugins/inputs/github_webhooks"
_ "github.com/influxdata/telegraf/plugins/inputs/haproxy"
_ "github.com/influxdata/telegraf/plugins/inputs/http_response"
Expand Down
37 changes: 37 additions & 0 deletions plugins/inputs/filestat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# filestat Input Plugin

The filestat plugin gathers metrics about file existence, size, and other stats.

### Configuration:

```toml
# Read stats about given file(s)
[[inputs.filestat]]
## Files to gather stats about.
## These accept standard unix glob matching rules, but with the addition of
## ** as a "super asterisk". See https://github.com/gobwas/glob.
files = ["/etc/telegraf/telegraf.conf", "/var/log/**.log"]
## If true, read the entire file and calculate an md5 checksum.
md5 = false
```

### Measurements & Fields:

- filestat
- exists (int, 0 | 1)
- size_bytes (int, bytes)
- md5 (optional, string)

### Tags:

- All measurements have the following tags:
- file (the path the to file, as specified in the config)

### Example Output:

```
$ telegraf -config /etc/telegraf/telegraf.conf -input-filter filestat -test
* Plugin: filestat, Collection 1
> filestat,file=/tmp/foo/bar,host=tyrion exists=0i 1461203374493128216
> filestat,file=/Users/sparrc/ws/telegraf.conf,host=tyrion exists=1i,size=47894i 1461203374493199335
```
185 changes: 185 additions & 0 deletions plugins/inputs/filestat/filestat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package filestat

import (
"crypto/md5"
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/gobwas/glob"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)

var sepStr = fmt.Sprintf("%v", string(os.PathSeparator))

const sampleConfig = `
## Files to gather stats about.
## These accept standard unix glob matching rules, but with the addition of
## ** as a "super asterisk". See https://github.com/gobwas/glob.
["/etc/telegraf/telegraf.conf", "/var/log/**.log"]
## If true, read the entire file and calculate an md5 checksum.
md5 = false
`

type FileStat struct {
Md5 bool
Files []string

// maps full file paths to glob obj
globs map[string]glob.Glob
// maps full file paths to their root dir
roots map[string]string
}

func NewFileStat() *FileStat {
return &FileStat{
globs: make(map[string]glob.Glob),
roots: make(map[string]string),
}
}

func (_ *FileStat) Description() string {
return "Read stats about given file(s)"
}

func (_ *FileStat) SampleConfig() string { return sampleConfig }

func (f *FileStat) Gather(acc telegraf.Accumulator) error {
var errS string
var err error

for _, filepath := range f.Files {
// Get the compiled glob object for this filepath
g, ok := f.globs[filepath]
if !ok {
if g, err = glob.Compile(filepath, os.PathSeparator); err != nil {
errS += err.Error() + " "
continue
}
f.globs[filepath] = g
}
// Get the root directory for this filepath
root, ok := f.roots[filepath]
if !ok {
root = findRootDir(filepath)
f.roots[filepath] = root
}

var matches []string
// Do not walk file tree if we don't have to.
if !hasMeta(filepath) {
matches = []string{filepath}
} else {
matches = walkFilePath(f.roots[filepath], f.globs[filepath])
}
for _, file := range matches {
tags := map[string]string{
"file": file,
}
fields := map[string]interface{}{
"exists": int64(0),
}
// Get file stats
fileInfo, err := os.Stat(file)
if os.IsNotExist(err) {
// file doesn't exist, so move on to the next
acc.AddFields("filestat", fields, tags)
continue
}
if err != nil {
errS += err.Error() + " "
continue
}

// file exists and no errors encountered
fields["exists"] = int64(1)
fields["size_bytes"] = fileInfo.Size()

if f.Md5 {
md5, err := getMd5(file)
if err != nil {
errS += err.Error() + " "
} else {
fields["md5_sum"] = md5
}
}

acc.AddFields("filestat", fields, tags)
}
}

if errS != "" {
return fmt.Errorf(errS)
}
return nil
}

// walk the filepath from the given root and return a list of files that match
// the given glob.
func walkFilePath(root string, g glob.Glob) []string {
matchedFiles := []string{}
walkfn := func(path string, _ os.FileInfo, _ error) error {
if g.Match(path) {
matchedFiles = append(matchedFiles, path)
}
return nil
}
filepath.Walk(root, walkfn)
return matchedFiles
}

// Read given file and calculate an md5 hash.
func getMd5(file string) (string, error) {
of, err := os.Open(file)
if err != nil {
return "", err
}
defer of.Close()

hash := md5.New()
_, err = io.Copy(hash, of)
if err != nil {
// fatal error
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}

// find the root dir of the given path (could include globs).
// ie:
// /var/log/telegraf.conf -> /var/log/
// /home/** -> /home/
// /home/*/** -> /home/
// /lib/share/*/*/**.txt -> /lib/share/
func findRootDir(path string) string {
pathItems := strings.Split(path, sepStr)
outpath := sepStr
for i, item := range pathItems {
if i == len(pathItems)-1 {
break
}
if item == "" {
continue
}
if hasMeta(item) {
break
}
outpath += item + sepStr
}
return outpath
}

// hasMeta reports whether path contains any magic glob characters.
func hasMeta(path string) bool {
return strings.IndexAny(path, "*?[") >= 0
}

func init() {
inputs.Add("filestat", func() telegraf.Input {
return NewFileStat()
})
}
Loading