diff --git a/README.md b/README.md index ad02091da..b913bfc71 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ configuration options. * [nstat](./plugins/inputs/nstat) * [ntpq](./plugins/inputs/ntpq) * [openldap](./plugins/inputs/openldap) +* [opensmtpd](./plugins/inputs/opensmtpd) * [phpfpm](./plugins/inputs/phpfpm) * [phusion passenger](./plugins/inputs/passenger) * [ping](./plugins/inputs/ping) diff --git a/etc/telegraf.conf b/etc/telegraf.conf index e805bb467..069b657b6 100644 --- a/etc/telegraf.conf +++ b/etc/telegraf.conf @@ -1876,6 +1876,18 @@ # bind_password = "" +# # A plugin to collect stats from OpenSMTPd +# [[inputs.opensmtpd]] +# ## If running as a restricted user you can prepend sudo for additional access: +# #use_sudo = false +# +# ## The default location of the smtpctl binary can be overridden with: +# #binary = "/usr/sbin/smtpctl" +# +# ## The default timeout of 1s can be overriden with: +# #timeout = "1s" + + # # Read metrics of passenger using passenger-status # [[inputs.passenger]] # ## Path of passenger-status. diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 0adbb8abb..e5cc96ed9 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -60,6 +60,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/nstat" _ "github.com/influxdata/telegraf/plugins/inputs/ntpq" _ "github.com/influxdata/telegraf/plugins/inputs/openldap" + _ "github.com/influxdata/telegraf/plugins/inputs/opensmtpd" _ "github.com/influxdata/telegraf/plugins/inputs/passenger" _ "github.com/influxdata/telegraf/plugins/inputs/phpfpm" _ "github.com/influxdata/telegraf/plugins/inputs/ping" diff --git a/plugins/inputs/opensmtpd/README.md b/plugins/inputs/opensmtpd/README.md new file mode 100644 index 000000000..c1166d9e5 --- /dev/null +++ b/plugins/inputs/opensmtpd/README.md @@ -0,0 +1,102 @@ +# OpenSMTPD Input Plugin + +This plugin gathers stats from [OpenSMTPD - a FREE implementation of the server-side SMTP protocol](https://www.opensmtpd.org/) + +### Configuration: + +```toml + # A plugin to collect stats from OpenSMTPD - a FREE implementation of the server-side SMTP protocol + [[inputs.smtpctl]] + ## If running as a restricted user you can prepend sudo for additional access: + #use_sudo = false + + ## The default location of the smtpctl binary can be overridden with: + binary = "/usr/sbin/smtpctl" + + # The default timeout of 1s can be overriden with: + #timeout = "1s" +``` + +### Measurements & Fields: + +This is the full list of stats provided by smtpctl and potentially collected by telegram +depending of your smtpctl configuration. + +- smtpctl + bounce_envelope + bounce_message + bounce_session + control_session + mda_envelope + mda_pending + mda_running + mda_user + mta_connector + mta_domain + mta_envelope + mta_host + mta_relay + mta_route + mta_session + mta_source + mta_task + mta_task_running + queue_bounce + queue_evpcache_load_hit + queue_evpcache_size + queue_evpcache_update_hit + scheduler_delivery_ok + scheduler_delivery_permfail + scheduler_delivery_tempfail + scheduler_envelope + scheduler_envelope_expired + scheduler_envelope_incoming + scheduler_envelope_inflight + scheduler_ramqueue_envelope + scheduler_ramqueue_message + scheduler_ramqueue_update + smtp_session + smtp_session_inet4 + smtp_session_local + uptime + +### Permissions: + +It's important to note that this plugin references smtpctl, which may require additional permissions to execute successfully. +Depending on the user/group permissions of the telegraf user executing this plugin, you may need to alter the group membership, set facls, or use sudo. + +**Group membership (Recommended)**: +```bash +$ groups telegraf +telegraf : telegraf + +$ usermod -a -G opensmtpd telegraf + +$ groups telegraf +telegraf : telegraf opensmtpd +``` + +**Sudo privileges**: +If you use this method, you will need the following in your telegraf config: +```toml +[[inputs.opensmtpd]] + use_sudo = true +``` + +You will also need to update your sudoers file: +```bash +$ visudo +# Add the following line: +telegraf ALL=(ALL) NOPASSWD: /usr/sbin/smtpctl +``` + +Please use the solution you see as most appropriate. + +### Example Output: + +``` + telegraf --config etc/telegraf.conf --input-filter opensmtpd --test +* Plugin: inputs.opensmtpd, Collection 1 +> opensmtpd,host=localhost scheduler_delivery_tempfail=822,mta_host=10,mta_task_running=4,queue_bounce=13017,scheduler_delivery_permfail=51022,mta_relay=7,queue_evpcache_size=2,scheduler_envelope_expired=26,bounce_message=0,mta_domain=7,queue_evpcache_update_hit=848,smtp_session_local=12294,bounce_envelope=0,queue_evpcache_load_hit=4389703,scheduler_ramqueue_update=0,mta_route=3,scheduler_delivery_ok=2149489,smtp_session_inet4=2131997,control_session=1,scheduler_envelope_incoming=0,uptime=10346728,scheduler_ramqueue_envelope=2,smtp_session=0,bounce_session=0,mta_envelope=2,mta_session=6,mta_task=2,scheduler_ramqueue_message=2,mta_connector=7,mta_source=1,scheduler_envelope=2,scheduler_envelope_inflight=2 1510220300000000000 + +``` diff --git a/plugins/inputs/opensmtpd/opensmtpd.go b/plugins/inputs/opensmtpd/opensmtpd.go new file mode 100644 index 000000000..1c0e5690d --- /dev/null +++ b/plugins/inputs/opensmtpd/opensmtpd.go @@ -0,0 +1,134 @@ +package opensmtpd + +import ( + "bufio" + "bytes" + "fmt" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/filter" + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/plugins/inputs" +) + +type runner func(cmdName string, Timeout internal.Duration, UseSudo bool) (*bytes.Buffer, error) + +// Opensmtpd is used to store configuration values +type Opensmtpd struct { + Binary string + Timeout internal.Duration + UseSudo bool + + filter filter.Filter + run runner +} + +var defaultBinary = "/usr/sbin/smtpctl" +var defaultTimeout = internal.Duration{Duration: time.Second} + +var sampleConfig = ` + ## If running as a restricted user you can prepend sudo for additional access: + #use_sudo = false + + ## The default location of the smtpctl binary can be overridden with: + binary = "/usr/sbin/smtpctl" + + ## The default timeout of 1000ms can be overriden with (in milliseconds): + timeout = 1000 +` + +func (s *Opensmtpd) Description() string { + return "A plugin to collect stats from Opensmtpd - a validating, recursive, and caching DNS resolver " +} + +// SampleConfig displays configuration instructions +func (s *Opensmtpd) SampleConfig() string { + return sampleConfig +} + +// Shell out to opensmtpd_stat and return the output +func opensmtpdRunner(cmdName string, Timeout internal.Duration, UseSudo bool) (*bytes.Buffer, error) { + cmdArgs := []string{"show", "stats"} + + cmd := exec.Command(cmdName, cmdArgs...) + + if UseSudo { + cmdArgs = append([]string{cmdName}, cmdArgs...) + cmd = exec.Command("sudo", cmdArgs...) + } + + var out bytes.Buffer + cmd.Stdout = &out + err := internal.RunTimeout(cmd, Timeout.Duration) + if err != nil { + return &out, fmt.Errorf("error running smtpctl: %s", err) + } + + return &out, nil +} + +// Gather collects the configured stats from smtpctl and adds them to the +// Accumulator +// +// All the dots in stat name will replaced by underscores. Histogram statistics will not be collected. +func (s *Opensmtpd) Gather(acc telegraf.Accumulator) error { + // Always exclude uptime.human statistics + stat_excluded := []string{"uptime.human"} + filter_excluded, err := filter.Compile(stat_excluded) + if err != nil { + return err + } + + out, err := s.run(s.Binary, s.Timeout, s.UseSudo) + if err != nil { + return fmt.Errorf("error gathering metrics: %s", err) + } + + // Process values + fields := make(map[string]interface{}) + scanner := bufio.NewScanner(out) + for scanner.Scan() { + + cols := strings.Split(scanner.Text(), "=") + + // Check split correctness + if len(cols) != 2 { + continue + } + + stat := cols[0] + value := cols[1] + + // Filter value + if filter_excluded.Match(stat) { + continue + } + + field := strings.Replace(stat, ".", "_", -1) + + fields[field], err = strconv.ParseFloat(value, 64) + if err != nil { + acc.AddError(fmt.Errorf("Expected a numerical value for %s = %v\n", + stat, value)) + } + } + + acc.AddFields("opensmtpd", fields, nil) + + return nil +} + +func init() { + inputs.Add("opensmtpd", func() telegraf.Input { + return &Opensmtpd{ + run: opensmtpdRunner, + Binary: defaultBinary, + Timeout: defaultTimeout, + UseSudo: false, + } + }) +} diff --git a/plugins/inputs/opensmtpd/opensmtpd_test.go b/plugins/inputs/opensmtpd/opensmtpd_test.go new file mode 100644 index 000000000..42e978b6c --- /dev/null +++ b/plugins/inputs/opensmtpd/opensmtpd_test.go @@ -0,0 +1,111 @@ +package opensmtpd + +import ( + "bytes" + "testing" + "time" + + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" +) + +var TestTimeout = internal.Duration{Duration: time.Second} + +func SmtpCTL(output string, Timeout internal.Duration, useSudo bool) func(string, internal.Duration, bool) (*bytes.Buffer, error) { + return func(string, internal.Duration, bool) (*bytes.Buffer, error) { + return bytes.NewBuffer([]byte(output)), nil + } +} + +func TestFilterSomeStats(t *testing.T) { + acc := &testutil.Accumulator{} + v := &Opensmtpd{ + run: SmtpCTL(fullOutput, TestTimeout, false), + } + err := v.Gather(acc) + + assert.NoError(t, err) + assert.True(t, acc.HasMeasurement("opensmtpd")) + assert.Equal(t, acc.NMetrics(), uint64(1)) + + assert.Equal(t, acc.NFields(), 36) + acc.AssertContainsFields(t, "opensmtpd", parsedFullOutput) +} + +var parsedFullOutput = map[string]interface{}{ + "bounce_envelope": float64(0), + "bounce_message": float64(0), + "bounce_session": float64(0), + "control_session": float64(1), + "mda_envelope": float64(0), + "mda_pending": float64(0), + "mda_running": float64(0), + "mda_user": float64(0), + "mta_connector": float64(1), + "mta_domain": float64(1), + "mta_envelope": float64(0), + "mta_host": float64(6), + "mta_relay": float64(1), + "mta_route": float64(1), + "mta_session": float64(1), + "mta_source": float64(1), + "mta_task": float64(0), + "mta_task_running": float64(5), + "queue_bounce": float64(11495), + "queue_evpcache_load_hit": float64(3927539), + "queue_evpcache_size": float64(0), + "queue_evpcache_update_hit": float64(508), + "scheduler_delivery_ok": float64(1922951), + "scheduler_delivery_permfail": float64(45967), + "scheduler_delivery_tempfail": float64(493), + "scheduler_envelope": float64(0), + "scheduler_envelope_expired": float64(17), + "scheduler_envelope_incoming": float64(0), + "scheduler_envelope_inflight": float64(0), + "scheduler_ramqueue_envelope": float64(0), + "scheduler_ramqueue_message": float64(0), + "scheduler_ramqueue_update": float64(0), + "smtp_session": float64(0), + "smtp_session_inet4": float64(1903412), + "smtp_session_local": float64(10827), + "uptime": float64(9253995), +} + +var fullOutput = `bounce.envelope=0 +bounce.message=0 +bounce.session=0 +control.session=1 +mda.envelope=0 +mda.pending=0 +mda.running=0 +mda.user=0 +mta.connector=1 +mta.domain=1 +mta.envelope=0 +mta.host=6 +mta.relay=1 +mta.route=1 +mta.session=1 +mta.source=1 +mta.task=0 +mta.task.running=5 +queue.bounce=11495 +queue.evpcache.load.hit=3927539 +queue.evpcache.size=0 +queue.evpcache.update.hit=508 +scheduler.delivery.ok=1922951 +scheduler.delivery.permfail=45967 +scheduler.delivery.tempfail=493 +scheduler.envelope=0 +scheduler.envelope.expired=17 +scheduler.envelope.incoming=0 +scheduler.envelope.inflight=0 +scheduler.ramqueue.envelope=0 +scheduler.ramqueue.message=0 +scheduler.ramqueue.update=0 +smtp.session=0 +smtp.session.inet4=1903412 +smtp.session.local=10827 +uptime=9253995 +uptime.human=107d2h33m15s`