sensu-plugins-docker-swarm 3.3.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 +7 -0
- data/CHANGELOG.md +179 -0
- data/LICENSE +22 -0
- data/README.md +40 -0
- data/bin/check-container-logs.rb +199 -0
- data/bin/check-container.rb +93 -0
- data/bin/check-docker-container.rb +108 -0
- data/bin/check-service-logs.rb +199 -0
- data/bin/check-service.rb +102 -0
- data/bin/metrics-docker-container.rb +127 -0
- data/bin/metrics-docker-info.rb +65 -0
- data/bin/metrics-docker-stats.rb +219 -0
- data/lib/sensu-plugins-docker.rb +1 -0
- data/lib/sensu-plugins-docker/client_helpers.rb +60 -0
- data/lib/sensu-plugins-docker/version.rb +9 -0
- metadata +271 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# docker-container-metrics
|
4
|
+
#
|
5
|
+
# DESCRIPTION:
|
6
|
+
#
|
7
|
+
# OUTPUT:
|
8
|
+
# metric-data
|
9
|
+
#
|
10
|
+
# PLATFORMS:
|
11
|
+
# Linux
|
12
|
+
#
|
13
|
+
# DEPENDENCIES:
|
14
|
+
# gem: sensu-plugin
|
15
|
+
#
|
16
|
+
# USAGE:
|
17
|
+
#
|
18
|
+
# metrics-docker-container.rb -H /var/run/docker.sock -n
|
19
|
+
# docker.host.sensu.sh.rss 1 1508825823
|
20
|
+
# docker.host.sensu.sh.vsize 1572864 1508825823
|
21
|
+
# docker.host.sensu.sh.nswap 0 1508825823
|
22
|
+
# docker.host.sensu.sh.pctmem 0.0 1508825823
|
23
|
+
# docker.host.sensu.sh.fd 0 1508825823
|
24
|
+
# docker.host.sensu.sh.cpu 0 1508825823
|
25
|
+
#
|
26
|
+
# metrics-docker-container.rb -H https://127.0.0.1:2376
|
27
|
+
# docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.rss 183 1508825793
|
28
|
+
# docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.vsize 4616192 1508825793
|
29
|
+
# docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.nswap 0 1508825793
|
30
|
+
# docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.pctmem 0.01 1508825793
|
31
|
+
# docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.fd 0 1508825793
|
32
|
+
# docker.host.ff5240e079488d248c021f8da5d13074a6a9db72ffaf1129eded445f4e16cf50.sh.cpu 0 1508825793
|
33
|
+
#
|
34
|
+
# NOTES:
|
35
|
+
#
|
36
|
+
# LICENSE:
|
37
|
+
# Copyright 2014 Michal Cichra. Github @mikz
|
38
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
39
|
+
# for details.
|
40
|
+
#
|
41
|
+
|
42
|
+
require 'sensu-plugin/metric/cli'
|
43
|
+
require 'sensu-plugins-docker/client_helpers'
|
44
|
+
require 'pathname'
|
45
|
+
require 'sys/proctable'
|
46
|
+
|
47
|
+
#
|
48
|
+
# Docker Container Metrics
|
49
|
+
#
|
50
|
+
class DockerContainerMetrics < Sensu::Plugin::Metric::CLI::Graphite
|
51
|
+
option :scheme,
|
52
|
+
description: 'Metric naming scheme, text to prepend to metric',
|
53
|
+
short: '-s SCHEME',
|
54
|
+
long: '--scheme SCHEME',
|
55
|
+
default: "docker.#{Socket.gethostname}"
|
56
|
+
|
57
|
+
option :cgroup_path,
|
58
|
+
description: 'path to cgroup mountpoint',
|
59
|
+
short: '-c PATH',
|
60
|
+
long: '--cgroup PATH',
|
61
|
+
default: '/sys/fs/cgroup'
|
62
|
+
|
63
|
+
option :docker_host,
|
64
|
+
description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path',
|
65
|
+
short: '-H DOCKER_HOST',
|
66
|
+
long: '--docker-host DOCKER_HOST'
|
67
|
+
|
68
|
+
option :cgroup_template,
|
69
|
+
description: 'cgroup_template',
|
70
|
+
short: '-T TPL_STRING',
|
71
|
+
long: '--cgroup-template TPL_STRING',
|
72
|
+
default: 'cpu/docker/%{container}/cgroup.procs'
|
73
|
+
|
74
|
+
option :friendly_names,
|
75
|
+
description: 'use friendly name if available',
|
76
|
+
short: '-n',
|
77
|
+
long: '--names',
|
78
|
+
boolean: true,
|
79
|
+
default: false
|
80
|
+
|
81
|
+
def run
|
82
|
+
@client = DockerApi.new(config[:docker_host])
|
83
|
+
container_metrics
|
84
|
+
ok
|
85
|
+
end
|
86
|
+
|
87
|
+
def container_metrics
|
88
|
+
cgroup = "#{config[:cgroup_path]}/#{config[:cgroup_template]}"
|
89
|
+
|
90
|
+
timestamp = Time.now.to_i
|
91
|
+
ps = Sys::ProcTable.ps.group_by(&:pid)
|
92
|
+
sleep(1)
|
93
|
+
ps2 = Sys::ProcTable.ps.group_by(&:pid)
|
94
|
+
|
95
|
+
fields = [:rss, :vsize, :nswap, :pctmem]
|
96
|
+
|
97
|
+
path = '/containers/json'
|
98
|
+
containers = @client.parse(path)
|
99
|
+
|
100
|
+
containers.each do |container|
|
101
|
+
path = Pathname(format(cgroup, container: container['Id']))
|
102
|
+
pids = path.readlines.map(&:to_i)
|
103
|
+
|
104
|
+
container_name = if config[:friendly_names]
|
105
|
+
container['Names'][0].delete('/')
|
106
|
+
else
|
107
|
+
container['Id']
|
108
|
+
end
|
109
|
+
|
110
|
+
processes = ps.values_at(*pids).flatten.compact.group_by(&:comm)
|
111
|
+
processes2 = ps2.values_at(*pids).flatten.compact.group_by(&:comm)
|
112
|
+
|
113
|
+
processes.each do |comm, process|
|
114
|
+
prefix = "#{config[:scheme]}.#{container_name}.#{comm}"
|
115
|
+
fields.each do |field|
|
116
|
+
output "#{prefix}.#{field}", process.map(&field).reduce(:+), timestamp
|
117
|
+
end
|
118
|
+
# this check requires a lot of permissions, even root maybe?
|
119
|
+
output "#{prefix}.fd", process.map { |p| p.fd.keys.count }.reduce(:+), timestamp
|
120
|
+
|
121
|
+
second = processes2[comm]
|
122
|
+
cpu = second.map { |p| p.utime + p.stime }.reduce(:+) - process.map { |p| p.utime + p.stime }.reduce(:+)
|
123
|
+
output "#{prefix}.cpu", cpu, timestamp
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# metrics-docker-info
|
4
|
+
#
|
5
|
+
# DESCRIPTION:
|
6
|
+
#
|
7
|
+
# This check gather certain general stats from Docker (number of CPUs, number of containers, images...)
|
8
|
+
# Supports the stats feature of the docker remote api ( docker server 1.5 and newer )
|
9
|
+
# Supports connecting to docker remote API over Unix socket or TCP
|
10
|
+
# Based on metrics-docker-stats by @paulczar
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# OUTPUT:
|
14
|
+
# metric-data
|
15
|
+
#
|
16
|
+
# PLATFORMS:
|
17
|
+
# Linux
|
18
|
+
#
|
19
|
+
# DEPENDENCIES:
|
20
|
+
# gem: sensu-plugin
|
21
|
+
#
|
22
|
+
# USAGE:
|
23
|
+
# Gather stats using unix socket:
|
24
|
+
# metrics-docker-info.rb -H /var/run/docker.sock
|
25
|
+
#
|
26
|
+
# Gather stats from localhost using HTTP:
|
27
|
+
# metrics-docker-info.rb -H localhost:2375
|
28
|
+
#
|
29
|
+
# See metrics-docker-info.rb --help for full usage flags
|
30
|
+
#
|
31
|
+
# NOTES:
|
32
|
+
#
|
33
|
+
# LICENSE:
|
34
|
+
# Copyright 2017 Alfonso Casimiro. Github @alcasim
|
35
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
36
|
+
# for details.
|
37
|
+
#
|
38
|
+
|
39
|
+
require 'sensu-plugin/metric/cli'
|
40
|
+
require 'sensu-plugins-docker/client_helpers'
|
41
|
+
|
42
|
+
class DockerStatsMetrics < Sensu::Plugin::Metric::CLI::Graphite
|
43
|
+
option :scheme,
|
44
|
+
description: 'Metric naming scheme, text to prepend to metric',
|
45
|
+
short: '-s SCHEME',
|
46
|
+
long: '--scheme SCHEME',
|
47
|
+
default: "#{Socket.gethostname}.docker"
|
48
|
+
|
49
|
+
option :docker_host,
|
50
|
+
description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path',
|
51
|
+
short: '-H DOCKER_HOST',
|
52
|
+
long: '--docker-host DOCKER_HOST'
|
53
|
+
|
54
|
+
def run
|
55
|
+
@timestamp = Time.now.to_i
|
56
|
+
@client = DockerApi.new(config[:docker_host])
|
57
|
+
path = '/info'
|
58
|
+
infolist = @client.parse(path)
|
59
|
+
filtered_list = infolist.select { |key, _value| key.match(/NCPU|NFd|Containers|Images|NGoroutines|NEventsListener|MemTotal/) }
|
60
|
+
filtered_list.each do |key, value|
|
61
|
+
output "#{config[:scheme]}.#{key}", value, @timestamp
|
62
|
+
end
|
63
|
+
ok
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# metrics-docker-stats
|
4
|
+
#
|
5
|
+
# DESCRIPTION:
|
6
|
+
#
|
7
|
+
# Supports the stats feature of the docker remote api ( docker server 1.5 and newer )
|
8
|
+
# Supports connecting to docker remote API over Unix socket or TCP
|
9
|
+
#
|
10
|
+
#
|
11
|
+
# OUTPUT:
|
12
|
+
# metric-data
|
13
|
+
#
|
14
|
+
# PLATFORMS:
|
15
|
+
# Linux
|
16
|
+
#
|
17
|
+
# DEPENDENCIES:
|
18
|
+
# gem: sensu-plugin
|
19
|
+
#
|
20
|
+
# USAGE:
|
21
|
+
# Gather stats from all containers on a host using socket:
|
22
|
+
# metrics-docker-stats.rb -H /var/run/docker.sock
|
23
|
+
#
|
24
|
+
# Gather stats from all containers on a host using HTTP:
|
25
|
+
# metrics-docker-stats.rb -H localhost:2375
|
26
|
+
#
|
27
|
+
# Gather stats from a specific container using socket:
|
28
|
+
# metrics-docker-stats.rb -H /var/run/docker.sock -N 5bf1b82382eb
|
29
|
+
#
|
30
|
+
# See metrics-docker-stats.rb --help for full usage flags
|
31
|
+
#
|
32
|
+
# NOTES:
|
33
|
+
#
|
34
|
+
# LICENSE:
|
35
|
+
# Copyright 2015 Paul Czarkowski. Github @paulczar
|
36
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
37
|
+
# for details.
|
38
|
+
#
|
39
|
+
|
40
|
+
require 'sensu-plugin/metric/cli'
|
41
|
+
require 'sensu-plugins-docker/client_helpers'
|
42
|
+
|
43
|
+
class Hash
|
44
|
+
def self.to_dotted_hash(hash, recursive_key = '')
|
45
|
+
hash.each_with_object({}) do |(k, v), ret|
|
46
|
+
key = recursive_key + k.to_s
|
47
|
+
if v.is_a? Hash
|
48
|
+
ret.merge! to_dotted_hash(v, key + '.')
|
49
|
+
else
|
50
|
+
ret[key] = v
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class DockerStatsMetrics < Sensu::Plugin::Metric::CLI::Graphite
|
57
|
+
option :scheme,
|
58
|
+
description: 'Metric naming scheme, text to prepend to metric',
|
59
|
+
short: '-s SCHEME',
|
60
|
+
long: '--scheme SCHEME',
|
61
|
+
default: "#{Socket.gethostname}.docker"
|
62
|
+
|
63
|
+
option :container,
|
64
|
+
description: 'Name of container to collect metrics for',
|
65
|
+
short: '-N CONTAINER',
|
66
|
+
long: '--container-name CONTAINER',
|
67
|
+
default: ''
|
68
|
+
|
69
|
+
option :docker_host,
|
70
|
+
description: 'Docker API URI. https://host, https://host:port, http://host, http://host:port, host:port, unix:///path',
|
71
|
+
short: '-H DOCKER_HOST',
|
72
|
+
long: '--docker-host DOCKER_HOST'
|
73
|
+
|
74
|
+
option :friendly_names,
|
75
|
+
description: 'use friendly name if available',
|
76
|
+
short: '-n',
|
77
|
+
long: '--names',
|
78
|
+
boolean: true,
|
79
|
+
default: false
|
80
|
+
|
81
|
+
option :name_parts,
|
82
|
+
description: 'Partial names by spliting and returning at index(es).
|
83
|
+
eg. -m 3,4 my-docker-container-process_name-b2ffdab8f1aceae85300 for process_name.b2ffdab8f1aceae85300',
|
84
|
+
short: '-m index',
|
85
|
+
long: '--match index'
|
86
|
+
|
87
|
+
option :delim,
|
88
|
+
description: 'the deliminator to use with -m',
|
89
|
+
short: '-d',
|
90
|
+
long: '--deliminator',
|
91
|
+
default: '-'
|
92
|
+
|
93
|
+
option :environment_tags,
|
94
|
+
description: 'Name of environment variables on each container to be appended to metric name, separated by commas',
|
95
|
+
short: '-e ENV_VARS',
|
96
|
+
long: '--environment-tags ENV_VARS'
|
97
|
+
|
98
|
+
option :ioinfo,
|
99
|
+
description: 'enable IO Docker metrics',
|
100
|
+
short: '-i',
|
101
|
+
long: '--ioinfo',
|
102
|
+
boolean: true,
|
103
|
+
default: false
|
104
|
+
|
105
|
+
option :cpupercent,
|
106
|
+
description: 'add cpu usage percentage metric',
|
107
|
+
short: '-P',
|
108
|
+
long: '--percentage',
|
109
|
+
boolean: true,
|
110
|
+
default: false
|
111
|
+
|
112
|
+
def run
|
113
|
+
@timestamp = Time.now.to_i
|
114
|
+
@client = DockerApi.new(config[:docker_host])
|
115
|
+
|
116
|
+
list = if config[:container] != ''
|
117
|
+
[config[:container]]
|
118
|
+
else
|
119
|
+
list_containers
|
120
|
+
end
|
121
|
+
list.each do |container|
|
122
|
+
stats = container_stats(container)
|
123
|
+
scheme = ''
|
124
|
+
unless config[:environment_tags].nil?
|
125
|
+
scheme << container_tags(container)
|
126
|
+
end
|
127
|
+
if config[:name_parts]
|
128
|
+
config[:name_parts].split(',').each do |key|
|
129
|
+
scheme << '.' unless scheme == ''
|
130
|
+
scheme << container.split(config[:delim])[key.to_i]
|
131
|
+
end
|
132
|
+
else
|
133
|
+
scheme << container
|
134
|
+
end
|
135
|
+
output_stats(scheme, stats)
|
136
|
+
end
|
137
|
+
ok
|
138
|
+
end
|
139
|
+
|
140
|
+
def output_stats(container, stats)
|
141
|
+
dotted_stats = Hash.to_dotted_hash stats
|
142
|
+
dotted_stats.each do |key, value|
|
143
|
+
next if key == 'read' # unecessary timestamp
|
144
|
+
next if value.is_a?(Array)
|
145
|
+
value.delete!('/') if key == 'name'
|
146
|
+
output "#{config[:scheme]}.#{container}.#{key}", value, @timestamp
|
147
|
+
end
|
148
|
+
if config[:ioinfo]
|
149
|
+
blkio_stats(stats['blkio_stats']).each do |key, value|
|
150
|
+
output "#{config[:scheme]}.#{container}.#{key}", value, @timestamp
|
151
|
+
end
|
152
|
+
end
|
153
|
+
output "#{config[:scheme]}.#{container}.cpu_stats.usage_percent", calculate_cpu_percent(stats), @timestamp if config[:cpupercent]
|
154
|
+
end
|
155
|
+
|
156
|
+
def list_containers
|
157
|
+
list = []
|
158
|
+
path = '/containers/json'
|
159
|
+
containers = @client.parse(path)
|
160
|
+
|
161
|
+
containers.each do |container|
|
162
|
+
list << if config[:friendly_names]
|
163
|
+
container['Names'][-1].delete('/')
|
164
|
+
elsif config[:name_parts]
|
165
|
+
container['Names'][-1].delete('/')
|
166
|
+
else
|
167
|
+
container['Id']
|
168
|
+
end
|
169
|
+
end
|
170
|
+
list
|
171
|
+
end
|
172
|
+
|
173
|
+
def container_stats(container)
|
174
|
+
path = "/containers/#{container}/stats?stream=0"
|
175
|
+
response = @client.call(path)
|
176
|
+
if response.code.to_i == 404
|
177
|
+
critical "#{config[:container]} is not running on #{@client.uri}"
|
178
|
+
end
|
179
|
+
parse_json(response)
|
180
|
+
end
|
181
|
+
|
182
|
+
def container_tags(container)
|
183
|
+
tags = ''
|
184
|
+
path = "/containers/#{container}/json"
|
185
|
+
response = @client.call(path)
|
186
|
+
if response.code.to_i == 404
|
187
|
+
critical "#{config[:container]} is not running on #{@client.uri}"
|
188
|
+
end
|
189
|
+
inspect = parse_json(response)
|
190
|
+
tag_list = config[:environment_tags].split(',')
|
191
|
+
tag_list.each do |value|
|
192
|
+
tags << inspect['Config']['Env'].select { |tag| tag.to_s.match(/#{value}=/) }.first.to_s.gsub(/#{value}=/, '') + '.'
|
193
|
+
end
|
194
|
+
tags
|
195
|
+
end
|
196
|
+
|
197
|
+
def blkio_stats(io_stats)
|
198
|
+
stats_out = {}
|
199
|
+
io_stats.each do |stats_type, stats_vals|
|
200
|
+
stats_vals.each do |value|
|
201
|
+
stats_out["#{stats_type}.#{value['op']}.#{value['major']}.#{value['minor']}"] = value['value']
|
202
|
+
end
|
203
|
+
end
|
204
|
+
stats_out
|
205
|
+
end
|
206
|
+
|
207
|
+
def calculate_cpu_percent(stats)
|
208
|
+
cpu_percent = 0.0
|
209
|
+
previous_cpu = stats['precpu_stats']['cpu_usage']['total_usage']
|
210
|
+
previous_system = stats['precpu_stats']['system_cpu_usage']
|
211
|
+
cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - previous_cpu
|
212
|
+
system_delta = stats['cpu_stats']['system_cpu_usage'] - previous_system
|
213
|
+
if system_delta > 0 && cpu_delta > 0
|
214
|
+
number_of_cpu = stats['cpu_stats']['cpu_usage']['percpu_usage'].length
|
215
|
+
cpu_percent = (cpu_delta.to_f / system_delta.to_f) * number_of_cpu * 100
|
216
|
+
end
|
217
|
+
format('%.2f', cpu_percent)
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'sensu-plugins-docker/version'
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'net_http_unix'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class DockerApi
|
5
|
+
def initialize(uri = nil)
|
6
|
+
@client = nil
|
7
|
+
@docker_uri = uri || ENV['DOCKER_URL'] || ENV['DOCKER_HOST'] || '/var/run/docker.sock'
|
8
|
+
if @docker_uri.sub!(%r{^(unix://)?/}, '')
|
9
|
+
@docker_uri = 'unix:///' + @docker_uri
|
10
|
+
@client = NetX::HTTPUnix.new(@docker_uri)
|
11
|
+
else
|
12
|
+
protocol = %r{^(https?|tcp)://}.match(@docker_uri) || 'http://'
|
13
|
+
@docker_uri.sub!(protocol.to_s, '')
|
14
|
+
split_host = @docker_uri.split ':'
|
15
|
+
@client = if split_host.length == 2
|
16
|
+
NetX::HTTPUnix.new("#{protocol}#{split_host[0]}", split_host[1])
|
17
|
+
else
|
18
|
+
NetX::HTTPUnix.new("#{protocol}#{@docker_uri}", 2375)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
@client.start
|
22
|
+
end
|
23
|
+
|
24
|
+
def uri
|
25
|
+
@docker_uri
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(path, halt = true, limit = 10)
|
29
|
+
raise ArgumentError, "HTTP redirect too deep. Last url called : #{path}" if limit.zero?
|
30
|
+
if %r{^unix:///} =~ @docker_uri
|
31
|
+
request = Net::HTTP::Get.new path.to_s
|
32
|
+
else
|
33
|
+
uri = URI("#{@docker_uri}#{path}")
|
34
|
+
request = Net::HTTP::Get.new uri.request_uri
|
35
|
+
end
|
36
|
+
response = @client.request(request)
|
37
|
+
case response
|
38
|
+
when Net::HTTPSuccess then response
|
39
|
+
when Net::HTTPRedirection then call(response['location'], true, limit - 1)
|
40
|
+
else
|
41
|
+
return response.error! unless halt == false
|
42
|
+
return response
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse(path, halt = true, limit = 10)
|
47
|
+
parsed = parse_json(call(path, halt, limit))
|
48
|
+
parsed
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_json(response)
|
53
|
+
parsed = nil
|
54
|
+
begin
|
55
|
+
parsed = JSON.parse(response.read_body)
|
56
|
+
rescue JSON::ParserError => e
|
57
|
+
raise "JSON Error: #{e.inspect}"
|
58
|
+
end
|
59
|
+
parsed
|
60
|
+
end
|