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

Input plugin opensmtpd #3449

Merged
merged 7 commits into from
Nov 20, 2017
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions etc/telegraf.conf
Original file line number Diff line number Diff line change
Expand Up @@ -1863,6 +1863,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.
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 @@ -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"
Expand Down
102 changes: 102 additions & 0 deletions plugins/inputs/opensmtpd/README.md
Original file line number Diff line number Diff line change
@@ -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

```
134 changes: 134 additions & 0 deletions plugins/inputs/opensmtpd/opensmtpd.go
Original file line number Diff line number Diff line change
@@ -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,
}
})
}
111 changes: 111 additions & 0 deletions plugins/inputs/opensmtpd/opensmtpd_test.go
Original file line number Diff line number Diff line change
@@ -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`