postfix-exporter 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|