diff --git a/CHANGELOG.md b/CHANGELOG.md index 548ea9938..d6bb53291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ parsing JSON data as it does now. ### Features - [#652](https://github.com/influxdata/telegraf/pull/652): CouchDB Input Plugin - [#655](https://github.com/influxdata/telegraf/pull/655): Support parsing arbitrary data formats. Currently limited to kafka_consumer and exec inputs. +- [#671](https://github.com/influxdata/telegraf/pull/671): Dovecot input plugin. ### Bugfixes - [#443](https://github.com/influxdata/telegraf/issues/443): Fix Ping command timeout parameter on Linux. diff --git a/README.md b/README.md index c5db3c6e2..6109e0841 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,7 @@ Currently implemented sources: * couchdb * disque * docker +* dovecot * elasticsearch * exec (generic executable plugin, support JSON, influx and graphite) * haproxy diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index f263c3f68..e7329b042 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -7,6 +7,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/couchdb" _ "github.com/influxdata/telegraf/plugins/inputs/disque" _ "github.com/influxdata/telegraf/plugins/inputs/docker" + _ "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/github_webhooks" diff --git a/plugins/inputs/dovecot/README.md b/plugins/inputs/dovecot/README.md new file mode 100644 index 000000000..3ec7ac02e --- /dev/null +++ b/plugins/inputs/dovecot/README.md @@ -0,0 +1,67 @@ +# Dovecot Input Plugin + +The dovecot plugin uses the dovecot Stats protocol to gather metrics on configured +domains. You can read Dovecot's documentation +[here](http://wiki2.dovecot.org/Statistics) + + +### Configuration: + +``` +# Read metrics about dovecot servers +[[inputs.dovecot]] + # Dovecot servers + # specify dovecot servers via an address:port list + # e.g. + # localhost:24242 + # + # If no servers are specified, then localhost is used as the host. + servers = ["localhost:24242"] + # Only collect metrics for these domains, collect all if empty + domains = [] +``` + + +### Tags: + server: hostname + domain: domain name + + +### Fields: + + reset_timestamp time.Time + last_update time.Time + num_logins int64 + num_cmds int64 + num_connected_sessions int64 + user_cpu float32 + sys_cpu float32 + clock_time float64 + min_faults int64 + maj_faults int64 + vol_cs int64 + invol_cs int64 + disk_input int64 + disk_output int64 + read_count int64 + read_bytes int64 + write_count int64 + write_bytes int64 + mail_lookup_path int64 + mail_lookup_attr int64 + mail_read_count int64 + mail_read_bytes int64 + mail_cache_hits int64 + + +### Example Output: + +``` +telegraf -config telegraf.cfg -input-filter dovecot -test +* Plugin: dovecot, Collection 1 +> dovecot,domain=xxxxx.it,server=dovecot--1.mail.sys clock_time=12105746411632.5,disk_input=115285225472i,disk_output=4885067755520i,invol_cs=169701886i,last_update="2016-02-09 08:49:47.000014113 +0100 CET",mail_cache_hits=441828i,mail_lookup_attr=0i,mail_lookup_path=25323i,mail_read_bytes=241188145i,mail_read_count=11719i,maj_faults=3168i,min_faults=321438988i,num_cmds=51635i,num_connected_sessions=2i,num_logins=17149i,read_bytes=7939026951110i,read_count=3716991752i,reset_timestamp="2016-01-28 09:34:36 +0100 CET",sys_cpu=222595.288,user_cpu=267468.08,vol_cs=3288715920i,write_bytes=4483648967059i,write_count=1640646952i 1455004219924838345 +> dovecot,domain=yyyyy.com,server=dovecot-1.mail.sys clock_time=6650794455331782,disk_input=61957695569920i,disk_output=2638244004487168i,invol_cs=2004805041i,last_update="2016-02-09 08:49:49.000251296 +0100 CET",mail_cache_hits=2499112513i,mail_lookup_attr=506730i,mail_lookup_path=39128227i,mail_read_bytes=1076496874501i,mail_read_count=32615262i,maj_faults=1643304i,min_faults=4216116325i,num_cmds=85785559i,num_connected_sessions=1177i,num_logins=11658255i,read_bytes=4289150974554145i,read_count=1112000703i,reset_timestamp="2016-01-28 09:31:26 +0100 CET",sys_cpu=121125923.032,user_cpu=145561336.428,vol_cs=205451885i,write_bytes=2420130526835796i,write_count=2991367252i 1455004219925152529 +> dovecot,domain=xxxxx.it,server=dovecot-2.mail.sys clock_time=10710826586999.143,disk_input=79792410624i,disk_output=4496066158592i,invol_cs=150426876i,last_update="2016-02-09 08:48:19.000209134 +0100 CET",mail_cache_hits=5480869i,mail_lookup_attr=0i,mail_lookup_path=122563i,mail_read_bytes=340746273i,mail_read_count=44275i,maj_faults=1722i,min_faults=288071875i,num_cmds=50098i,num_connected_sessions=0i,num_logins=16389i,read_bytes=7259551999517i,read_count=3396625369i,reset_timestamp="2016-01-28 09:31:29 +0100 CET",sys_cpu=200762.792,user_cpu=242477.664,vol_cs=2996657358i,write_bytes=4133381575263i,write_count=1497242759i 1455004219924888283 +> dovecot,domain=yyyyy.com,server=dovecot-2.mail.sys clock_time=6522131245483702,disk_input=48259150004224i,disk_output=2754333359087616i,invol_cs=2294595260i,last_update="2016-02-09 08:49:49.000251919 +0100 CET",mail_cache_hits=2139113611i,mail_lookup_attr=520276i,mail_lookup_path=37940318i,mail_read_bytes=1088002215022i,mail_read_count=31350271i,maj_faults=994420i,min_faults=1486260543i,num_cmds=40414997i,num_connected_sessions=978i,num_logins=11259672i,read_bytes=4445546612487315i,read_count=1763534543i,reset_timestamp="2016-01-28 09:31:24 +0100 CET",sys_cpu=123655962.668,user_cpu=149259327.032,vol_cs=4215130546i,write_bytes=2531186030222761i,write_count=2186579650i 1455004219925398372 +``` + diff --git a/plugins/inputs/dovecot/dovecot.go b/plugins/inputs/dovecot/dovecot.go new file mode 100644 index 000000000..de9ef0cfe --- /dev/null +++ b/plugins/inputs/dovecot/dovecot.go @@ -0,0 +1,166 @@ +package dovecot + +import ( + "bytes" + "fmt" + "io" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +type Dovecot struct { + Servers []string + Domains []string +} + +func (d *Dovecot) Description() string { + return "Read statistics from one or many dovecot servers" +} + +var sampleConfig = ` + ### specify dovecot servers via an address:port list + ### e.g. + ### localhost:24242 + ### + ### If no servers are specified, then localhost is used as the host. + servers = ["localhost:24242"] + ### Only collect metrics for these domains, collect all if empty + domains = [] +` + +func (d *Dovecot) SampleConfig() string { return sampleConfig } + +const defaultPort = "24242" + +// Reads stats from all configured servers. +func (d *Dovecot) Gather(acc telegraf.Accumulator) error { + + if len(d.Servers) == 0 { + d.Servers = append(d.Servers, "127.0.0.1:24242") + } + + var wg sync.WaitGroup + + var outerr error + + var domains = make(map[string]bool) + + for _, dom := range d.Domains { + domains[dom] = true + } + + for _, serv := range d.Servers { + wg.Add(1) + go func(serv string) { + defer wg.Done() + outerr = d.gatherServer(serv, acc, domains) + }(serv) + } + + wg.Wait() + + return outerr +} + +func (d *Dovecot) gatherServer(addr string, acc telegraf.Accumulator, doms map[string]bool) error { + _, _, err := net.SplitHostPort(addr) + if err != nil { + return fmt.Errorf("Error: %s on url %s\n", err, addr) + } + + c, err := net.Dial("tcp", addr) + if err != nil { + return fmt.Errorf("Unable to connect to dovecot server '%s': %s", addr, err) + } + defer c.Close() + + c.Write([]byte("EXPORT\tdomain\n\n")) + var buf bytes.Buffer + io.Copy(&buf, c) + // buf := bufio.NewReader(c) + + host, _, _ := net.SplitHostPort(addr) + + return gatherStats(&buf, acc, doms, host) +} + +func gatherStats(buf *bytes.Buffer, acc telegraf.Accumulator, doms map[string]bool, host string) error { + + lines := strings.Split(buf.String(), "\n") + head := strings.Split(lines[0], "\t") + vals := lines[1:] + + for i := range vals { + if vals[i] == "" { + continue + } + val := strings.Split(vals[i], "\t") + fields := make(map[string]interface{}) + if len(doms) > 0 && !doms[val[0]] { + continue + } + tags := map[string]string{"server": host, "domain": val[0]} + for n := range val { + switch head[n] { + case "domain": + continue + // fields[head[n]] = val[n] + case "user_cpu", "sys_cpu", "clock_time": + fields[head[n]] = secParser(val[n]) + case "reset_timestamp", "last_update": + fields[head[n]] = timeParser(val[n]) + default: + ival, _ := splitSec(val[n]) + fields[head[n]] = ival + } + } + + acc.AddFields("dovecot", fields, tags) + } + + return nil +} + +func splitSec(tm string) (sec int64, msec int64) { + var err error + ss := strings.Split(tm, ".") + + sec, err = strconv.ParseInt(ss[0], 10, 64) + if err != nil { + sec = 0 + } + if len(ss) > 1 { + msec, err = strconv.ParseInt(ss[1], 10, 64) + if err != nil { + msec = 0 + } + } else { + msec = 0 + } + + return sec, msec +} + +func timeParser(tm string) time.Time { + + sec, msec := splitSec(tm) + return time.Unix(sec, msec) +} + +func secParser(tm string) float64 { + + sec, msec := splitSec(tm) + return float64(sec) + (float64(msec) / 1000000.0) +} + +func init() { + inputs.Add("dovecot", func() telegraf.Input { + return &Dovecot{} + }) +} diff --git a/plugins/inputs/dovecot/dovecot_test.go b/plugins/inputs/dovecot/dovecot_test.go new file mode 100644 index 000000000..76425c0db --- /dev/null +++ b/plugins/inputs/dovecot/dovecot_test.go @@ -0,0 +1,61 @@ +package dovecot + +import ( + "bytes" + "testing" + "time" + + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" +) + +func TestDovecot(t *testing.T) { + + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + var acc testutil.Accumulator + tags := map[string]string{"server": "dovecot.test", "domain": "domain.test"} + buf := bytes.NewBufferString(sampleStats) + + var doms = map[string]bool{ + "domain.test": true, + } + + err := gatherStats(buf, &acc, doms, "dovecot.test") + require.NoError(t, err) + + fields := map[string]interface{}{ + "reset_timestamp": time.Unix(1453969886, 0), + "last_update": time.Unix(1454603963, 39864), + "num_logins": int64(7503897), + "num_cmds": int64(52595715), + "num_connected_sessions": int64(1204), + "user_cpu": 1.00831175372e+08, + "sys_cpu": 8.3849071112e+07, + "clock_time": 4.3260019315281835e+15, + "min_faults": int64(763950011), + "maj_faults": int64(1112443), + "vol_cs": int64(4120386897), + "invol_cs": int64(3685239306), + "disk_input": int64(41679480946688), + "disk_output": int64(1819070669176832), + "read_count": int64(2368906465), + "read_bytes": int64(2957928122981169), + "write_count": int64(3545389615), + "write_bytes": int64(1666822498251286), + "mail_lookup_path": int64(24396105), + "mail_lookup_attr": int64(302845), + "mail_read_count": int64(20155768), + "mail_read_bytes": int64(669946617705), + "mail_cache_hits": int64(1557255080), + } + + acc.AssertContainsTaggedFields(t, "dovecot", fields, tags) + +} + +const sampleStats = `domain reset_timestamp last_update num_logins num_cmds num_connected_sessions user_cpu sys_cpu clock_time min_faults maj_faults vol_cs invol_cs disk_input disk_output read_count read_bytes write_count write_bytes mail_lookup_path mail_lookup_attr mail_read_count mail_read_bytes mail_cache_hits +domain.bad 1453970076 1454603947.383029 10749 33828 0 177988.524000 148071.772000 7531838964717.193706 212491179 2125 2190386067 112779200 74487934976 3221808119808 2469948401 5237602841760 1091171292 2951966459802 15363 0 2922 136403379 334372 +domain.test 1453969886 1454603963.039864 7503897 52595715 1204 100831175.372000 83849071.112000 4326001931528183.495762 763950011 1112443 4120386897 3685239306 41679480946688 1819070669176832 2368906465 2957928122981169 3545389615 1666822498251286 24396105 302845 20155768 669946617705 1557255080`