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.
@@ -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