Skip to content

Commit

Permalink
Remake Prometheus endpoint into a proper endpoint module
Browse files Browse the repository at this point in the history
  • Loading branch information
foxcpp committed Aug 23, 2020
1 parent bb77f8e commit f58da8a
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 32 deletions.
1 change: 1 addition & 0 deletions .mkdocs.yml
Expand Up @@ -24,6 +24,7 @@ nav:
- unicode.md
- upgrading.md
- specifications.md
- openmetrics.md
- Manual pages:
- man/_generated_maddy.1.md
- man/_generated_maddy-auth.5.md
Expand Down
11 changes: 11 additions & 0 deletions docs/man/maddy.5.scd
Expand Up @@ -181,6 +181,17 @@ logrotate daemon. Send SIGUSR1 to maddy process to make it reopen log files.
Enable verbose logging for all modules. You don't need that unless you are
reporting a bug.

# Prometheus/OpenMetrics endpoint

```
openmetrics tcp://127.0.0.1:9749 { }
```

This will enable HTTP listener that will serve telemetry in OpenMetrics format.
(It is compatible with Prometheus).

See openmetrics.md documentation page the list of metrics exposed.

# Signals

*SIGTERM, SIGINT, SIGHUP*
Expand Down
40 changes: 40 additions & 0 deletions docs/openmetrics.md
@@ -0,0 +1,40 @@
# OpenMetrics/Promethus telemetry

Various server statistics is provided in OpenMetrics format by "openmetrics"
module.

To enable it, add following line to the server config:
```
openmetrics tcp://127.0.0.1:9749 { }
```

Scrape endpoint would be `http://127.0.0.1:9749/metrics`.

## Metrics

```
# AUTH command failures due to invalid credentials.
maddy_smtp_failed_logins{module}
# Failed SMTP transaction commands (MAIL, RCPT, DATA).
maddy_smtp_failed_commands{module, command, smtp_code, smtp_enchcode}
# Messages rejected with 4xx code due to ratelimiting.
maddy_smtp_ratelimit_deferred{module}
# Amount of started SMTP trasanactions started.
maddy_smtp_started_transactions{module}
# Amount of aborted SMTP trasanactions started.
maddy_smtp_aborted_transactions{module}
# Amount of completed SMTP trasanactions.
maddy_smtp_completed_transactions{module}
# Number of times a check returned 'reject' result (may be more than processed
# messages if check does so on per-recipient basis)
maddy_check_reject{check}
# Number of times a check returned 'quarantine' result (may be more than
# processed messages if check does so on per-recipient basis).
maddy_check_quarantined{check}
# Amount of queued messages
maddy_queue_length{module, location}
# Outbound connections established with specific TLS security level
maddy_remote_conns_tls_level{module, level}
# Outbound connections established with specific MX security level
maddy_remote_conns_mx_level{module, level}
```
106 changes: 106 additions & 0 deletions internal/endpoint/openmetrics/om.go
@@ -0,0 +1,106 @@
/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package openmetrics

import (
"fmt"
"net"
"net/http"
"sync"

"github.com/foxcpp/maddy/framework/config"
"github.com/foxcpp/maddy/framework/log"
"github.com/foxcpp/maddy/framework/module"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

const modName = "openmetrics"

type Endpoint struct {
addrs []string
logger log.Logger

listenersWg sync.WaitGroup
serv http.Server
mux *http.ServeMux
}

func New(_ string, args []string) (module.Module, error) {
return &Endpoint{
addrs: args,
logger: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug},
}, nil
}

func (e *Endpoint) Init(cfg *config.Map) error {
cfg.Bool("debug", false, false, &e.logger.Debug)
if _, err := cfg.Process(); err != nil {
return err
}

e.mux = http.NewServeMux()
e.mux.Handle("/metrics", promhttp.Handler())
e.serv.Handler = e.mux

for _, a := range e.addrs {
a := a
endp, err := config.ParseEndpoint(a)
if err != nil {
return fmt.Errorf("%s: malformed endpoint: %v", modName, err)
}
if endp.IsTLS() {
return fmt.Errorf("%s: TLS is not supported yet", modName)
}
l, err := net.Listen(endp.Network(), endp.Address())
if err != nil {
return fmt.Errorf("%s: %v", modName, err)
}

e.listenersWg.Add(1)
go func() {
e.logger.Println("listening on", endp.String())
err := e.serv.Serve(l)
if err != nil && err != http.ErrServerClosed {
e.logger.Error("serve failed", err, "endpoint", a)
}
}()
}

return nil
}

func (e *Endpoint) Name() string {
return modName
}

func (e *Endpoint) InstanceName() string {
return ""
}

func (e *Endpoint) Close() error {
if err := e.serv.Close(); err != nil {
return err
}
e.listenersWg.Wait()
return nil
}

func init() {
module.RegisterEndpoint(modName, New)
}
2 changes: 1 addition & 1 deletion internal/endpoint/smtp/metrics.go
Expand Up @@ -72,7 +72,7 @@ var (
Namespace: "maddy",
Subsystem: "smtp",
Name: "failed_commands",
Help: "Messages rejected with 4xx code due to ratelimiting",
Help: "Failed transaction commands (MAIL, RCPT, DATA)",
},
[]string{"module", "command", "smtp_code", "smtp_enchcode"},
)
Expand Down
32 changes: 1 addition & 31 deletions maddy.go
Expand Up @@ -23,7 +23,6 @@ import (
"flag"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
Expand All @@ -37,7 +36,6 @@ import (
"github.com/foxcpp/maddy/framework/hooks"
"github.com/foxcpp/maddy/framework/log"
"github.com/foxcpp/maddy/framework/module"
"github.com/prometheus/client_golang/prometheus/promhttp"

// Import packages for side-effect of module registration.
_ "github.com/foxcpp/maddy/internal/auth/dovecot_sasl"
Expand All @@ -56,6 +54,7 @@ import (
_ "github.com/foxcpp/maddy/internal/check/spf"
_ "github.com/foxcpp/maddy/internal/endpoint/dovecot_sasld"
_ "github.com/foxcpp/maddy/internal/endpoint/imap"
_ "github.com/foxcpp/maddy/internal/endpoint/openmetrics"
_ "github.com/foxcpp/maddy/internal/endpoint/smtp"
_ "github.com/foxcpp/maddy/internal/imap_filter"
_ "github.com/foxcpp/maddy/internal/imap_filter/command"
Expand Down Expand Up @@ -116,8 +115,6 @@ var (
profileEndpoint *string
blockProfileRate *int
mutexProfileFract *int

prometheusEndpoint string
)

func BuildInfo() string {
Expand Down Expand Up @@ -226,25 +223,6 @@ func initDebug() {
}
}

func startPrometheusHTTP(endpoint string) error {
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())

l, err := net.Listen("tcp", prometheusEndpoint)
if err != nil {
return err
}

log.Println("listening on", prometheusEndpoint, "for Prometheus scraping")
go func() {
err := http.Serve(l, mux)
if err != nil && err != http.ErrServerClosed {
log.Println("prometheus listener fail:", err)
}
}()
return nil
}

func InitDirs() error {
if config.StateDirectory == "" {
config.StateDirectory = DefaultStateDirectory
Expand Down Expand Up @@ -304,7 +282,6 @@ func ReadGlobals(cfg []config.Node) (map[string]interface{}, []config.Node, erro
globals := config.NewMap(nil, config.Node{Children: cfg})
globals.String("state_dir", false, false, DefaultStateDirectory, &config.StateDirectory)
globals.String("runtime_dir", false, false, DefaultRuntimeDirectory, &config.RuntimeDirectory)
globals.String("prometheus_endpoint", false, false, "", &prometheusEndpoint)
globals.String("hostname", false, false, "", nil)
globals.String("autogenerated_msg_domain", false, false, "", nil)
globals.Custom("tls", false, false, nil, tls.TLSDirective, nil)
Expand All @@ -324,13 +301,6 @@ func moduleMain(cfg []config.Node) error {
return err
}

// Set by ReadGlobals.
if prometheusEndpoint != "" {
if err := startPrometheusHTTP(prometheusEndpoint); err != nil {
return err
}
}

if err := InitDirs(); err != nil {
return err
}
Expand Down

0 comments on commit f58da8a

Please sign in to comment.