diff --git a/README.md b/README.md index b913bfc71cd4e..20c3c584cce9f 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ configuration options. * [phpfpm](./plugins/inputs/phpfpm) * [phusion passenger](./plugins/inputs/passenger) * [ping](./plugins/inputs/ping) +* [postfix](./plugins/inputs/postfix) * [postgresql](./plugins/inputs/postgresql) * [postgresql_extensible](./plugins/inputs/postgresql_extensible) * [powerdns](./plugins/inputs/powerdns) diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index e5cc96ed9a044..87de7b53e8e86 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -64,6 +64,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/passenger" _ "github.com/influxdata/telegraf/plugins/inputs/phpfpm" _ "github.com/influxdata/telegraf/plugins/inputs/ping" + _ "github.com/influxdata/telegraf/plugins/inputs/postfix" _ "github.com/influxdata/telegraf/plugins/inputs/postgresql" _ "github.com/influxdata/telegraf/plugins/inputs/postgresql_extensible" _ "github.com/influxdata/telegraf/plugins/inputs/powerdns" diff --git a/plugins/inputs/postfix/README.md b/plugins/inputs/postfix/README.md new file mode 100644 index 0000000000000..477a78c9bf0d6 --- /dev/null +++ b/plugins/inputs/postfix/README.md @@ -0,0 +1,36 @@ +# Postfix Input Plugin + +The postfix plugin reports metrics on the postfix queues. + +For each of the active, hold, incoming, maildrop, and deferred queues (http://www.postfix.org/QSHAPE_README.html#queues), it will report the queue length (number of items), size (bytes used by items), and age (age of oldest item in seconds). + +### Configuration + +```toml +[[inputs.postfix]] + ## Postfix queue directory. If not provided, telegraf will try to use + ## 'postconf -h queue_directory' to determine it. + # queue_directory = "/var/spool/postfix" +``` + +### Measurements & Fields: + +- postfix_queue + - length (integer) + - size (integer, bytes) + - age (integer, seconds) + +### Tags: + +- postfix_queue + - queue + +### Example Output + +``` +postfix_queue,queue=active length=3,size=12345,age=9 +postfix_queue,queue=hold length=0,size=0,age=0 +postfix_queue,queue=maildrop length=1,size=2000,age=2 +postfix_queue,queue=incoming length=1,size=1020,age=0 +postfix_queue,queue=deferred length=400,size=76543210,age=3600 +``` diff --git a/plugins/inputs/postfix/postfix.go b/plugins/inputs/postfix/postfix.go new file mode 100644 index 0000000000000..02b351d787bfc --- /dev/null +++ b/plugins/inputs/postfix/postfix.go @@ -0,0 +1,119 @@ +package postfix + +import ( + "fmt" + "os" + "os/exec" + "path" + "strings" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +const sampleConfig = ` + ## Postfix queue directory. If not provided, telegraf will try to use + ## 'postconf -h queue_directory' to determine it. + # queue_directory = "/var/spool/postfix" +` + +const description = "Measure postfix queue statistics" + +func getQueueDirectory() (string, error) { + qd, err := exec.Command("postconf", "-h", "queue_directory").Output() + if err != nil { + return "", err + } + return strings.TrimSpace(string(qd)), nil +} + +func qScan(path string) (int64, int64, int64, error) { + f, err := os.Open(path) + if err != nil { + return 0, 0, 0, err + } + + finfos, err := f.Readdir(-1) + f.Close() + if err != nil { + return 0, 0, 0, err + } + + var length, size int64 + var oldest time.Time + for _, finfo := range finfos { + length++ + size += finfo.Size() + if oldest.IsZero() || finfo.ModTime().Before(oldest) { + oldest = finfo.ModTime() + } + } + var age time.Duration + if !oldest.IsZero() { + age = time.Now().Sub(oldest) / time.Second + } + return length, size, int64(age), nil +} + +type Postfix struct { + QueueDirectory string +} + +func (p *Postfix) Gather(acc telegraf.Accumulator) error { + if p.QueueDirectory == "" { + var err error + p.QueueDirectory, err = getQueueDirectory() + if err != nil { + return fmt.Errorf("unable to determine queue directory: %s", err) + } + } + + for _, q := range []string{"active", "hold", "incoming", "maildrop"} { + length, size, age, err := qScan(path.Join(p.QueueDirectory, q)) + if err != nil { + acc.AddError(fmt.Errorf("error scanning queue %s: %s", q, err)) + continue + } + fields := map[string]interface{}{"length": length, "size": size, "age": age} + acc.AddFields("postfix_queue", fields, map[string]string{"queue": q}) + } + + var dLength, dSize, dAge int64 + for _, q := range []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"} { + length, size, age, err := qScan(path.Join(p.QueueDirectory, "deferred", q)) + if err != nil { + if os.IsNotExist(err) { + // the directories are created on first use + continue + } + acc.AddError(fmt.Errorf("error scanning queue deferred/%s: %s", q, err)) + return nil + } + dLength += length + dSize += size + if age > dAge { + dAge = age + } + } + fields := map[string]interface{}{"length": dLength, "size": dSize, "age": dAge} + acc.AddFields("postfix_queue", fields, map[string]string{"queue": "deferred"}) + + return nil +} + +func (p *Postfix) SampleConfig() string { + return sampleConfig +} + +func (p *Postfix) Description() string { + return description +} + +func init() { + inputs.Add("postfix", func() telegraf.Input { + return &Postfix{ + QueueDirectory: "/var/spool/postfix", + } + }) +} diff --git a/plugins/inputs/postfix/postfix_test.go b/plugins/inputs/postfix/postfix_test.go new file mode 100644 index 0000000000000..859d773c773cf --- /dev/null +++ b/plugins/inputs/postfix/postfix_test.go @@ -0,0 +1,63 @@ +package postfix + +import ( + "io/ioutil" + "os" + "path" + "testing" + "time" + + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGather(t *testing.T) { + td, err := ioutil.TempDir("", "") + require.NoError(t, err) + defer os.RemoveAll(td) + + for _, q := range []string{"active", "hold", "incoming", "maildrop", "deferred"} { + require.NoError(t, os.Mkdir(path.Join(td, q), 0755)) + } + for _, q := range []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "F"} { // "E" deliberately left off + require.NoError(t, os.Mkdir(path.Join(td, "deferred", q), 0755)) + } + + require.NoError(t, ioutil.WriteFile(path.Join(td, "active", "01"), []byte("abc"), 0644)) + require.NoError(t, ioutil.WriteFile(path.Join(td, "active", "02"), []byte("defg"), 0644)) + require.NoError(t, os.Chtimes(path.Join(td, "active", "02"), time.Now(), time.Now().Add(-time.Hour))) + require.NoError(t, ioutil.WriteFile(path.Join(td, "hold", "01"), []byte("abc"), 0644)) + require.NoError(t, ioutil.WriteFile(path.Join(td, "incoming", "01"), []byte("abcd"), 0644)) + require.NoError(t, ioutil.WriteFile(path.Join(td, "deferred", "0", "01"), []byte("abc"), 0644)) + require.NoError(t, ioutil.WriteFile(path.Join(td, "deferred", "F", "F1"), []byte("abc"), 0644)) + + p := Postfix{ + QueueDirectory: td, + } + + var acc testutil.Accumulator + require.NoError(t, p.Gather(&acc)) + + metrics := map[string]*testutil.Metric{} + for _, m := range acc.Metrics { + metrics[m.Tags["queue"]] = m + } + + assert.Equal(t, int64(2), metrics["active"].Fields["length"]) + assert.Equal(t, int64(7), metrics["active"].Fields["size"]) + assert.InDelta(t, int64(time.Hour/time.Second), metrics["active"].Fields["age"], 10) + + assert.Equal(t, int64(1), metrics["hold"].Fields["length"]) + assert.Equal(t, int64(3), metrics["hold"].Fields["size"]) + + assert.Equal(t, int64(1), metrics["incoming"].Fields["length"]) + assert.Equal(t, int64(4), metrics["incoming"].Fields["size"]) + + assert.Equal(t, int64(0), metrics["maildrop"].Fields["length"]) + assert.Equal(t, int64(0), metrics["maildrop"].Fields["size"]) + assert.Equal(t, int64(0), metrics["maildrop"].Fields["age"]) + + assert.Equal(t, int64(2), metrics["deferred"].Fields["length"]) + assert.Equal(t, int64(6), metrics["deferred"].Fields["size"]) +}