1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,92 @@
# Dovecot Input Plugin
This plugin uses the Dovecot [v2.1 stats protocol][stats] to gather
metrics on configured domains of [Dovecot][dovecot] servers. You can use this
plugin on Dovecot up to and including version v2.3.x.
> [!IMPORTANT]
> Dovecot v2.4+ has the old protocol removed and this plugin will not work.
> Please use Dovecot's [Openmetrics exporter][openmetrics] in combination with
> the [http input plugin][http_plugin] and `openmetrics` data format for newer
> versions of Dovecot.
⭐ Telegraf v0.10.3
🏷️ server
💻 all
[dovecot]: https://www.dovecot.org/
[stats]: https://doc.dovecot.org/configuration_manual/stats/old_statistics/#old-statistics
[http_plugin]: /plugins/inputs/http/README.md
[openmetrics]: https://doc.dovecot.org/latest/core/config/statistics.html#openmetrics
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Read metrics about dovecot servers
[[inputs.dovecot]]
## specify dovecot servers via an address:port list
## e.g.
## localhost:24242
## or as an UDS socket
## e.g.
## /var/run/dovecot/old-stats
##
## If no servers are specified, then localhost is used as the host.
servers = ["localhost:24242"]
## Type is one of "user", "domain", "ip", or "global"
type = "global"
## Wildcard matches like "*.com". An empty string "" is same as "*"
## If type = "ip" filters should be <IP/network>
filters = [""]
```
## Metrics
- dovecot
- tags:
- server (hostname)
- type (query type)
- ip (ip addr)
- user (username)
- domain (domain name)
- fields:
- reset_timestamp (string)
- last_update (string)
- num_logins (integer)
- num_cmds (integer)
- num_connected_sessions (integer)
- user_cpu (float)
- sys_cpu (float)
- clock_time (float)
- min_faults (integer)
- maj_faults (integer)
- vol_cs (integer)
- invol_cs (integer)
- disk_input (integer)
- disk_output (integer)
- read_count (integer)
- read_bytes (integer)
- write_count (integer)
- write_bytes (integer)
- mail_lookup_path (integer)
- mail_lookup_attr (integer)
- mail_read_count (integer)
- mail_read_bytes (integer)
- mail_cache_hits (integer)
## Example Output
```text
dovecot,server=dovecot-1.domain.test,type=global clock_time=101196971074203.94,disk_input=6493168218112i,disk_output=17978638815232i,invol_cs=1198855447i,last_update="2016-04-08 11:04:13.000379245 +0200 CEST",mail_cache_hits=68192209i,mail_lookup_attr=0i,mail_lookup_path=653861i,mail_read_bytes=86705151847i,mail_read_count=566125i,maj_faults=17208i,min_faults=1286179702i,num_cmds=917469i,num_connected_sessions=8896i,num_logins=174827i,read_bytes=30327690466186i,read_count=1772396430i,reset_timestamp="2016-04-08 10:28:45 +0200 CEST",sys_cpu=157965.692,user_cpu=219337.48,vol_cs=2827615787i,write_bytes=17150837661940i,write_count=992653220i 1460106266642153907
```

View file

@ -0,0 +1,197 @@
//go:generate ../../../tools/readme_config_includer/generator
package dovecot
import (
"bytes"
_ "embed"
"errors"
"fmt"
"io"
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
var (
defaultTimeout = time.Second * time.Duration(5)
validQuery = map[string]bool{
"user": true, "domain": true, "global": true, "ip": true,
}
)
type Dovecot struct {
Type string `toml:"type"`
Filters []string `toml:"filters"`
Servers []string `toml:"servers"`
}
func (*Dovecot) SampleConfig() string {
return sampleConfig
}
func (d *Dovecot) Gather(acc telegraf.Accumulator) error {
if !validQuery[d.Type] {
return fmt.Errorf("error: %s is not a valid query type", d.Type)
}
if len(d.Servers) == 0 {
d.Servers = append(d.Servers, "127.0.0.1:24242")
}
if len(d.Filters) == 0 {
d.Filters = append(d.Filters, "")
}
var wg sync.WaitGroup
for _, server := range d.Servers {
for _, filter := range d.Filters {
wg.Add(1)
go func(s string, f string) {
defer wg.Done()
acc.AddError(gatherServer(s, acc, d.Type, f))
}(server, filter)
}
}
wg.Wait()
return nil
}
func gatherServer(addr string, acc telegraf.Accumulator, qtype, filter string) error {
var proto string
if strings.HasPrefix(addr, "/") {
proto = "unix"
} else {
proto = "tcp"
_, _, err := net.SplitHostPort(addr)
if err != nil {
return fmt.Errorf("%w on url %q", err, addr)
}
}
c, err := net.DialTimeout(proto, addr, defaultTimeout)
if err != nil {
return fmt.Errorf("unable to connect to dovecot server %q: %w", addr, err)
}
defer c.Close()
// Extend connection
if err := c.SetDeadline(time.Now().Add(defaultTimeout)); err != nil {
return fmt.Errorf("setting deadline failed for dovecot server %q: %w", addr, err)
}
msg := "EXPORT\t" + qtype
if len(filter) > 0 {
msg += fmt.Sprintf("\t%s=%s", qtype, filter)
}
msg += "\n"
if _, err := c.Write([]byte(msg)); err != nil {
return fmt.Errorf("writing message %q failed for dovecot server %q: %w", msg, addr, err)
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, c); err != nil {
// We need to accept the timeout here as reading from the connection will only terminate on EOF
// or on a timeout to happen. As EOF for TCP connections will only be sent on connection closing,
// the only way to get the whole message is to wait for the timeout to happen.
var nerr net.Error
if !errors.As(err, &nerr) || !nerr.Timeout() {
return fmt.Errorf("copying message failed for dovecot server %q: %w", addr, err)
}
}
var host string
if strings.HasPrefix(addr, "/") {
host = addr
} else {
host, _, err = net.SplitHostPort(addr)
if err != nil {
return fmt.Errorf("reading address failed for dovecot server %q: %w", addr, err)
}
}
gatherStats(&buf, acc, host, qtype)
return nil
}
func gatherStats(buf *bytes.Buffer, acc telegraf.Accumulator, host, qtype string) {
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{})
tags := map[string]string{"server": host, "type": qtype}
if qtype != "global" {
tags[qtype] = val[0]
}
for n := range val {
switch head[n] {
case qtype:
continue
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)
}
}
func splitSec(tm string) (sec, 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{}
})
}

View file

@ -0,0 +1,228 @@
package dovecot
import (
"bufio"
"bytes"
"io"
"net"
"net/textproto"
"os"
"path/filepath"
"testing"
"time"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go/wait"
"github.com/influxdata/telegraf/testutil"
)
func TestDovecotIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
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),
}
var acc testutil.Accumulator
// Test type=global server=unix
addr := "/tmp/socket"
waitCh := make(chan int)
go func() {
defer close(waitCh)
la, err := net.ResolveUnixAddr("unix", addr)
if err != nil {
t.Error(err)
return
}
l, err := net.ListenUnix("unix", la)
if err != nil {
t.Error(err)
return
}
defer l.Close()
defer os.Remove(addr)
waitCh <- 0
conn, err := l.Accept()
if err != nil {
t.Error(err)
return
}
defer conn.Close()
readertp := textproto.NewReader(bufio.NewReader(conn))
if _, err = readertp.ReadLine(); err != nil {
t.Error(err)
return
}
buf := bytes.NewBufferString(sampleGlobal)
if _, err = io.Copy(conn, buf); err != nil {
t.Error(err)
return
}
}()
// Wait for server to start
<-waitCh
d := &Dovecot{Servers: []string{addr}, Type: "global"}
err := d.Gather(&acc)
require.NoError(t, err)
tags := map[string]string{"server": addr, "type": "global"}
acc.AssertContainsTaggedFields(t, "dovecot", fields, tags)
// Test type=global
tags = map[string]string{"server": "dovecot.test", "type": "global"}
buf := bytes.NewBufferString(sampleGlobal)
gatherStats(buf, &acc, "dovecot.test", "global")
acc.AssertContainsTaggedFields(t, "dovecot", fields, tags)
// Test type=domain
tags = map[string]string{"server": "dovecot.test", "type": "domain", "domain": "domain.test"}
buf = bytes.NewBufferString(sampleDomain)
gatherStats(buf, &acc, "dovecot.test", "domain")
acc.AssertContainsTaggedFields(t, "dovecot", fields, tags)
// Test type=ip
tags = map[string]string{"server": "dovecot.test", "type": "ip", "ip": "192.168.0.100"}
buf = bytes.NewBufferString(sampleIP)
gatherStats(buf, &acc, "dovecot.test", "ip")
acc.AssertContainsTaggedFields(t, "dovecot", fields, tags)
// Test type=user
fields = map[string]interface{}{
"reset_timestamp": time.Unix(1453969886, 0),
"last_update": time.Unix(1454603963, 39864),
"num_logins": int64(7503897),
"num_cmds": int64(52595715),
"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),
}
tags = map[string]string{"server": "dovecot.test", "type": "user", "user": "user.1@domain.test"}
buf = bytes.NewBufferString(sampleUser)
gatherStats(buf, &acc, "dovecot.test", "user")
acc.AssertContainsTaggedFields(t, "dovecot", fields, tags)
}
const sampleGlobal = `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
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`
const sampleDomain = `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.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`
const sampleIP = `ip 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
192.168.0.100 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`
const sampleUser = `user reset_timestamp last_update num_logins num_cmds 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
user.1@domain.test 1453969886 1454603963.039864 7503897 52595715 100831175.372000 83849071.112000 ` +
`4326001931528183.495762 763950011 1112443 4120386897 3685239306 41679480946688 1819070669176832 ` +
`2368906465 2957928122981169 3545389615 1666822498251286 24396105 302845 20155768 669946617705 1557255080`
func TestDovecotContainerIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping dovecot integration tests.")
}
testdata, err := filepath.Abs("testdata/dovecot.conf")
require.NoError(t, err, "determining absolute path of test-data failed")
servicePort := "24242"
container := testutil.Container{
Image: "dovecot/dovecot:2.3-latest",
ExposedPorts: []string{servicePort},
Files: map[string]string{
"/etc/dovecot/dovecot.conf": testdata,
},
WaitingFor: wait.ForAll(
wait.ForLog("starting up for imap"),
wait.ForListeningPort(nat.Port(servicePort)),
),
}
require.NoError(t, container.Start(), "failed to start container")
defer container.Terminate()
d := &Dovecot{
Servers: []string{container.Address + ":" + container.Ports[servicePort]},
Type: "global",
}
var acc testutil.Accumulator
require.NoError(t, d.Gather(&acc))
require.Eventually(t,
func() bool { return acc.HasMeasurement("dovecot") },
5*time.Second,
10*time.Millisecond,
)
require.True(t, acc.HasTag("dovecot", "type"))
require.True(t, acc.HasField("dovecot", "sys_cpu"))
require.True(t, acc.HasField("dovecot", "write_count"))
require.True(t, acc.HasField("dovecot", "mail_read_count"))
require.True(t, acc.HasField("dovecot", "auth_failures"))
}

View file

@ -0,0 +1,18 @@
# Read metrics about dovecot servers
[[inputs.dovecot]]
## specify dovecot servers via an address:port list
## e.g.
## localhost:24242
## or as an UDS socket
## e.g.
## /var/run/dovecot/old-stats
##
## If no servers are specified, then localhost is used as the host.
servers = ["localhost:24242"]
## Type is one of "user", "domain", "ip", or "global"
type = "global"
## Wildcard matches like "*.com". An empty string "" is same as "*"
## If type = "ip" filters should be <IP/network>
filters = [""]

View file

@ -0,0 +1,49 @@
## This is a default dovecot.conf file with the
## addition of the service stats section
mail_home=/srv/mail/%Lu
mail_location=sdbox:~/Mail
mail_uid=1000
mail_gid=1000
protocols = imap pop3 submission sieve lmtp
first_valid_uid = 1000
last_valid_uid = 1000
passdb {
driver = static
args = password=pass
}
ssl=yes
ssl_cert=<cert.pem
ssl_key=<key.pem
namespace {
inbox = yes
separator = /
}
service lmtp {
inet_listener {
port = 24
}
}
plugin {
old_stats_refresh = 30 secs
}
service old-stats {
inet_listener {
address = *
port = 24242
}
}
listen = *
log_path=/dev/stdout
info_log_path=/dev/stdout
debug_log_path=/dev/stdout