postfix-exporter 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Dockerfile +9 -0
- data/README.md +2 -17
- data/bin/postfix-exporter +14 -118
- data/postfix-exporter.gemspec +4 -4
- metadata +25 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c3d12e780c74fb42d02f4760e59b04b92d2697f5
|
4
|
+
data.tar.gz: 01f82ea6eacc6058db15b568e04494ad6bbdbf96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0be25eb7453abc769958db68daddddc64997cfd5d8a0ce5dd46f94b58f48b1c9bccf3c22a4e91c53f6e35f3b34e5f0f2f9cc2969073e43f7fd342b8647bb756b
|
7
|
+
data.tar.gz: 7178052ca7f931d8194a26d187e7d24541712e07f9f085f6d5e8d82ac642087f6009adf6786bb35bf6fad0d20fdf3a077dc149152954d55709700e2c76ef2a6f
|
data/Dockerfile
ADDED
data/README.md
CHANGED
@@ -7,18 +7,8 @@ server.
|
|
7
7
|
* Examines mail queue periodically and exports `postfix_mail_queue_size`;
|
8
8
|
|
9
9
|
* Reads syslog entries as they happen, and exports disposition status
|
10
|
-
counters (`
|
11
|
-
DSN
|
12
|
-
|
13
|
-
* Total number of SMTP connections (`postfix_smtpd_connections_total`) and
|
14
|
-
currently-active connections (`postfix_smtpd_active_connections`);
|
15
|
-
|
16
|
-
* Count how many delivery attempts were received
|
17
|
-
(`postfix_incoming_delivery_attempts_total`), split out by whether we
|
18
|
-
accepted or rejected the message (`status`) and the exact DSN provided to
|
19
|
-
the client (`dsn`);
|
20
|
-
|
21
|
-
* Whether or not the Postfix `master` process is running (`postfix_up`);
|
10
|
+
counters (`postfix_disposition`) per DSN, as well as delay summaries per
|
11
|
+
DSN;
|
22
12
|
|
23
13
|
* Drinks from the syslog stream directly.
|
24
14
|
|
@@ -31,11 +21,6 @@ available in the container as `/var/spool/postfix`), and some env
|
|
31
21
|
vars. You can also run it directly (via the gem), with the same env vars,
|
32
22
|
and with the expectation that `/var/spool/postfix` is in the usual place.
|
33
23
|
|
34
|
-
If you configure the postfix-exporter to run in the same PID namespace as
|
35
|
-
whatever it is that's running Postfix itself (either `--pid=host` or in the
|
36
|
-
same NS namespace, a la k8s pods), then the `postfix_up` metric will be
|
37
|
-
valid, otherwise it'll be random (but unlikely to be correct).
|
38
|
-
|
39
24
|
|
40
25
|
## Environment Variables
|
41
26
|
|
data/bin/postfix-exporter
CHANGED
@@ -1,19 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'rack'
|
4
|
-
require 'prometheus/middleware/exporter'
|
3
|
+
require 'prometheus/client/rack/exporter'
|
5
4
|
require 'socket'
|
5
|
+
require 'docker'
|
6
|
+
require 'rack'
|
6
7
|
require 'rack/handler/webrick'
|
7
8
|
require 'logger'
|
8
9
|
|
9
10
|
prometheus = Prometheus::Client.registry
|
10
11
|
|
11
|
-
prometheus.gauge(:
|
12
|
-
|
13
|
-
oldest = prometheus.gauge(:postfix_oldest_message_timestamp_seconds, "Queue time of the oldest message")
|
14
|
-
mailq = prometheus.gauge(:postfix_queue_size, "Number of messages in the mail queue")
|
15
|
-
q_err = prometheus.counter(:postfix_queue_processing_error_total, "Exceptions raised whilst scanning the Postfix queue")
|
16
|
-
up = prometheus.gauge(:postfix_up, "Whether the master process is running or not")
|
12
|
+
mailq = prometheus.gauge(:postfix_queue_size, "Number of messages in the mail queue")
|
17
13
|
|
18
14
|
Thread.abort_on_exception = true
|
19
15
|
|
@@ -27,84 +23,19 @@ Thread.new do
|
|
27
23
|
# deferred is special, because it's often hueg it gets sharded into
|
28
24
|
# multiple subdirectories
|
29
25
|
mailq.set({ queue: 'deferred' }, Dir["/var/spool/postfix/deferred/*/*"].size)
|
30
|
-
rescue StandardError => ex
|
31
|
-
$stderr.puts "Error while monitoring queue sizes: #{ex.message} (#{ex.class})"
|
32
|
-
$stderr.puts ex.backtrace.map { |l| " #{l}" }.join("\n")
|
33
|
-
q_err.increment(class: ex.class.to_s, phase: "scan")
|
34
|
-
end
|
35
|
-
|
36
|
-
begin
|
37
|
-
master_pid = File.read("/var/spool/postfix/pid/master.pid").to_i
|
38
26
|
|
39
|
-
|
40
|
-
Process.kill(0, master_pid)
|
41
|
-
# If we get here, then the process exists, and
|
42
|
-
# that'll do for our purposes
|
43
|
-
up.set({}, 1)
|
44
|
-
else
|
45
|
-
up.set({}, 0)
|
46
|
-
end
|
47
|
-
rescue Errno::ENOENT, Errno::ESRCH, Errno::EACCES
|
48
|
-
up.set({}, 0)
|
49
|
-
rescue Errno::EPERM
|
50
|
-
# Ironically, we don't need to be able to *actually*
|
51
|
-
# signal the process; EPERM means it exists and is running
|
52
|
-
# as someone more privileged than us, which is enough
|
53
|
-
# for our purposes
|
54
|
-
up.set({}, 1)
|
27
|
+
sleep 5
|
55
28
|
rescue StandardError => ex
|
56
|
-
$stderr.puts "Error while
|
57
|
-
$stderr.puts ex.backtrace.map { |l| " #{l}" }.join("\n")
|
58
|
-
q_err.increment(class: ex.class.to_s, phase: "up")
|
59
|
-
end
|
60
|
-
|
61
|
-
sleep 5
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
Thread.new do
|
67
|
-
earliest_ctime = ->(glob) do
|
68
|
-
# There is seemingly no way to unset or remove a gauge metric in the Ruby
|
69
|
-
# implementation of the prom exporter. As a hack, we return the current
|
70
|
-
# time in cases where there is nothing to sample.
|
71
|
-
now = Time.now.to_i
|
72
|
-
|
73
|
-
Dir[glob].lazy.map do |n|
|
74
|
-
begin
|
75
|
-
File.stat(n).ctime.to_i
|
76
|
-
rescue Errno::ENOENT
|
77
|
-
now
|
78
|
-
end
|
79
|
-
end.min || now
|
80
|
-
end
|
81
|
-
|
82
|
-
loop do
|
83
|
-
begin
|
84
|
-
%w{incoming active corrupt hold}.each do |q|
|
85
|
-
oldest.set({ queue: q }, earliest_ctime["/var/spool/postfix/#{q}/*"])
|
86
|
-
end
|
87
|
-
oldest.set({ queue: 'deferred' }, earliest_ctime["/var/spool/postfix/deferred/*/*"])
|
88
|
-
rescue StandardError => ex
|
89
|
-
$stderr.puts "Error while sampling message ages: #{ex.message} (#{ex.class})"
|
29
|
+
$stderr.puts "Error while monitoring queue sizes: #{ex.message} (#{ex.class})"
|
90
30
|
$stderr.puts ex.backtrace.map { |l| " #{l}" }.join("\n")
|
91
|
-
|
31
|
+
sleep 1
|
92
32
|
end
|
93
|
-
|
94
|
-
# stat()ing all the files in a large queue could potentially be quite
|
95
|
-
# expensive, so we sample this data less frequently.
|
96
|
-
sleep 60
|
97
|
-
|
98
33
|
end
|
99
34
|
end
|
100
35
|
|
101
36
|
if ENV["SYSLOG_SOCKET"]
|
102
|
-
delays
|
103
|
-
|
104
|
-
active = prometheus.gauge(:postfix_smtpd_active_connections, "Current connections to smtpd")
|
105
|
-
incoming = prometheus.counter(:postfix_incoming_delivery_attempts_total, "Delivery attempts, labelled by dsn and status")
|
106
|
-
messages = prometheus.counter(:postfix_log_messages_total, "Syslog messages received, labelled by how it was handled")
|
107
|
-
log_errors = prometheus.counter(:postfix_log_processing_error_total, "Exceptions raised whilst processing log messages")
|
37
|
+
delays = prometheus.summary(:postfix_delivery_delays, "Distribution of time taken to deliver (or bounce) messages")
|
38
|
+
statuses = prometheus.counter(:postfix_deliveries, "How many messages have been delivered (or bounced)")
|
108
39
|
|
109
40
|
Thread.new do
|
110
41
|
begin
|
@@ -118,44 +49,19 @@ if ENV["SYSLOG_SOCKET"]
|
|
118
49
|
loop do
|
119
50
|
begin
|
120
51
|
msg = s.recvmsg.first
|
121
|
-
if msg =~ %r{postfix
|
52
|
+
if msg =~ %r{postfix/smtp.* delay=(\d+(\.\d+)?), .* dsn=(\d+\.\d+\.\d+), status=(\w+)}
|
122
53
|
delay = $1.to_f
|
123
54
|
dsn = $3
|
124
55
|
status = $4
|
125
56
|
|
126
57
|
if status == "bounced" or status == "sent"
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
messages.increment(type: "delay")
|
131
|
-
elsif msg =~ %r{postfix/smtpd\[\d+\]: connect from }
|
132
|
-
connects.increment({})
|
133
|
-
active.send(:synchronize) { active.set({}, active.get({}) || 0 + 1) }
|
134
|
-
messages.increment(type: "connect")
|
135
|
-
elsif msg =~ %r{postfix/smtpd\[\d+\]: disconnect from }
|
136
|
-
active.send(:synchronize) do
|
137
|
-
new = (active.get({}) || 0) - 1
|
138
|
-
# If we start running mid-stream,
|
139
|
-
# we might end up seeing more
|
140
|
-
# disconnects than connections,
|
141
|
-
# which would be confusing
|
142
|
-
new = 0 if new < 0
|
143
|
-
active.set({}, new)
|
58
|
+
statuses.increment(dsn: dsn, status: status)
|
59
|
+
delays.add({dsn: dsn, status: status}, delay)
|
144
60
|
end
|
145
|
-
messages.increment(type: "disconnect")
|
146
|
-
elsif msg =~ %r{postfix/smtpd\[\d+\]: [A-F0-9]+: client=}
|
147
|
-
incoming.increment(dsn: "2.0.0", status: "queued")
|
148
|
-
messages.increment(type: "queued")
|
149
|
-
elsif msg =~ %r{postfix/smtpd\[\d+\]: NOQUEUE: reject: RCPT from \S+: \d{3} (\d+\.\d+\.\d+) }
|
150
|
-
incoming.increment(dsn: $1, status: "rejected")
|
151
|
-
messages.increment(type: "noqueue")
|
152
|
-
else
|
153
|
-
messages.increment(type: "ignored")
|
154
61
|
end
|
155
62
|
rescue StandardError => ex
|
156
63
|
$stderr.puts "Error while receiving postfix logs: #{ex.message} (#{ex.class})"
|
157
64
|
$stderr.puts ex.backtrace.map { |l| " #{l}" }.join("\n")
|
158
|
-
log_errors.increment(class: ex.class.to_s)
|
159
65
|
sleep 1
|
160
66
|
end
|
161
67
|
end
|
@@ -163,21 +69,11 @@ if ENV["SYSLOG_SOCKET"]
|
|
163
69
|
end
|
164
70
|
|
165
71
|
app = Rack::Builder.new
|
166
|
-
app.use Rack::
|
167
|
-
app.use Prometheus::Middleware::Exporter
|
72
|
+
app.use Prometheus::Client::Rack::Exporter
|
168
73
|
app.run ->(env) { [404, {'Content-Type' => 'text/plain'}, ['NOPE NOPE NOPE NOPE']] }
|
169
74
|
|
170
75
|
logger = Logger.new($stderr)
|
171
76
|
logger.level = Logger::INFO
|
172
77
|
logger.formatter = proc { |s, t, p, m| "WEBrick: #{m}\n" }
|
173
78
|
|
174
|
-
|
175
|
-
# INADDR_ANY and IN6ADDR_ANY on libcs that don't support getaddrinfo("*")
|
176
|
-
# (ie musl-libc). Setting `Host: '*'` barfs on the above-mentioned buggy(?)
|
177
|
-
# libcs, `Host: '::'` fails on newer rubies (because they use
|
178
|
-
# setsockopt(V6ONLY) by default), and with RACK_ENV at its default of
|
179
|
-
# "development", it only listens on localhost. And even *this* only works
|
180
|
-
# on Rack 2, because before that the non-development default listen address
|
181
|
-
# was "0.0.0.0"!
|
182
|
-
ENV['RACK_ENV'] = "none"
|
183
|
-
Rack::Handler::WEBrick.run app, Port: 9154, Logger: logger, AccessLog: []
|
79
|
+
Rack::Handler::WEBrick.run app, Host: '::', Port: 9154, Logger: logger, AccessLog: []
|
data/postfix-exporter.gemspec
CHANGED
@@ -18,15 +18,15 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.email = ["matt.palmer@discourse.org"]
|
19
19
|
s.homepage = "https://github.com/discourse/postfix-exporter"
|
20
20
|
|
21
|
-
s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(G|spec|
|
21
|
+
s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(G|spec|Rakefile)/ }
|
22
22
|
s.executables = ["postfix-exporter"]
|
23
23
|
|
24
24
|
s.required_ruby_version = ">= 2.1.0"
|
25
25
|
|
26
|
-
s.add_runtime_dependency 'prometheus-client'
|
27
|
-
s.add_runtime_dependency 'rack'
|
26
|
+
s.add_runtime_dependency 'prometheus-client'
|
27
|
+
s.add_runtime_dependency 'rack'
|
28
28
|
|
29
29
|
s.add_development_dependency 'bundler'
|
30
30
|
s.add_development_dependency 'github-release'
|
31
|
-
s.add_development_dependency 'rake'
|
31
|
+
s.add_development_dependency 'rake', '~> 10.4', '>= 10.4.2'
|
32
32
|
end
|
metadata
CHANGED
@@ -1,49 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: postfix-exporter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-09-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prometheus-client
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0.7'
|
20
|
-
- - "<"
|
17
|
+
- - ">="
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version: '0
|
19
|
+
version: '0'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- - "
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '0.7'
|
30
|
-
- - "<"
|
24
|
+
- - ">="
|
31
25
|
- !ruby/object:Gem::Version
|
32
|
-
version: '0
|
26
|
+
version: '0'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: rack
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
36
30
|
requirements:
|
37
|
-
- - "
|
31
|
+
- - ">="
|
38
32
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
33
|
+
version: '0'
|
40
34
|
type: :runtime
|
41
35
|
prerelease: false
|
42
36
|
version_requirements: !ruby/object:Gem::Requirement
|
43
37
|
requirements:
|
44
|
-
- - "
|
38
|
+
- - ">="
|
45
39
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
40
|
+
version: '0'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: bundler
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -76,17 +70,23 @@ dependencies:
|
|
76
70
|
name: rake
|
77
71
|
requirement: !ruby/object:Gem::Requirement
|
78
72
|
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.4'
|
79
76
|
- - ">="
|
80
77
|
- !ruby/object:Gem::Version
|
81
|
-
version:
|
78
|
+
version: 10.4.2
|
82
79
|
type: :development
|
83
80
|
prerelease: false
|
84
81
|
version_requirements: !ruby/object:Gem::Requirement
|
85
82
|
requirements:
|
83
|
+
- - "~>"
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '10.4'
|
86
86
|
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version:
|
89
|
-
description:
|
88
|
+
version: 10.4.2
|
89
|
+
description:
|
90
90
|
email:
|
91
91
|
- matt.palmer@discourse.org
|
92
92
|
executables:
|
@@ -95,13 +95,14 @@ extensions: []
|
|
95
95
|
extra_rdoc_files: []
|
96
96
|
files:
|
97
97
|
- ".gitignore"
|
98
|
+
- Dockerfile
|
98
99
|
- README.md
|
99
100
|
- bin/postfix-exporter
|
100
101
|
- postfix-exporter.gemspec
|
101
102
|
homepage: https://github.com/discourse/postfix-exporter
|
102
103
|
licenses: []
|
103
104
|
metadata: {}
|
104
|
-
post_install_message:
|
105
|
+
post_install_message:
|
105
106
|
rdoc_options: []
|
106
107
|
require_paths:
|
107
108
|
- lib
|
@@ -116,8 +117,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
117
|
- !ruby/object:Gem::Version
|
117
118
|
version: '0'
|
118
119
|
requirements: []
|
119
|
-
|
120
|
-
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.2.2
|
122
|
+
signing_key:
|
121
123
|
specification_version: 4
|
122
124
|
summary: Export Prometheus statistics for a Postfix server
|
123
125
|
test_files: []
|