Last active
February 17, 2016 18:09
-
-
Save blurredbits/1f716615998bb44d0be3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'excon' | |
require 'statsd-ruby' | |
require 'json' | |
module ExconConnection | |
class << self | |
def connection | |
Excon.new('unix:///', socket: socket_path) | |
end | |
def socket_path | |
unless(@socket_path) | |
if(File.exists?('/host/var/run/docker.sock')) | |
@socket_path = '/host/var/run/docker.sock' | |
else | |
@socket_path = '/var/run/docker.sock' | |
end | |
end | |
@socket_path | |
end | |
end | |
end | |
class Container | |
attr_reader :cid, :names, :stats, :prev_stats | |
def initialize(cid, names) | |
@cid = cid | |
@names = names | |
@stats = { memory_usage: 0.0, memory_limit: 0.0, network_in: 0.0, network_out: 0.0, cpu: 0.0, disk_io_read: 0.0 } | |
@prev_stats = { network_in: nil, network_out: nil, cpu: nil, disk_io_read: nil } | |
end | |
def get_stats! | |
ExconConnection.connection.request(method: :get, path: "/containers/#{self.cid}/stats", read_timeout: 10, response_block: streamer) | |
rescue Excon::Errors::Timeout | |
rescue Excon::Errors::SocketError => e | |
unless e.message.include?('stats gathered') | |
error("Invalid Stats API endpoint", "There was an error reading from the stats API. | |
Are you running Docker version 1.5+, and is /var/run/docker.sock readable by the user running scout?") | |
end | |
end | |
def calc_difference(stat) | |
@prev_stats[stat].nil? ? 0 : @stats[stat] - @prev_stats[stat] | |
end | |
def set_prev_stats(stat) | |
@prev_stats[stat] = @stats[stat] | |
end | |
private | |
def streamer | |
lambda do |chunk, remaining_bytes, total_bytes| | |
parse_stats(self.cid, chunk) | |
raise 'stats gathered' | |
end | |
end | |
def parse_stats(container_id, stats_string) | |
stats = JSON.parse(stats_string) | |
@stats[:memory_usage] = stats["memory_stats"]["usage"].to_f / 1024.0 / 1024.0 | |
@stats[:memory_limit] = stats["memory_stats"]["limit"].to_f / 1024.0 / 1024.0 | |
@stats[:network_in] = stats["network"]["rx_bytes"].to_f / 1024.0 | |
@stats[:network_out] = stats["network"]["tx_bytes"].to_f / 1024.0 | |
@stats[:cpu] = stats["cpu_stats"]["cpu_usage"]["total_usage"].to_f / 1024.0 / 1024.0 | |
@stats[:disk_io_read] = stats["blkio_stats"]["io_service_bytes_recursive"].first["value"].to_f / 1024.0 / 1024.0 | |
end | |
end | |
class ContainerMonitor | |
STATSD = Statsd.new('localhost', 8125) | |
attr_reader :containers | |
def initialize | |
@containers = [] | |
refresh_containers | |
end | |
def send_stats | |
refresh_containers | |
containers.each do |container| | |
container.get_stats! | |
container.stats.each do |key, value| | |
if key == :memory_limit | |
STATSD.gauge("docker.#{container.names.first}.#{key}", "#{value}") | |
else | |
STATSD.increment("docker.#{container.names.first}.#{key}", container.calc_difference(key)) | |
container.set_prev_stats(key) | |
end | |
end | |
end | |
end | |
private | |
def get_containers | |
response = ExconConnection.connection.request(method: :get, path: "/containers/json") | |
containers = JSON.parse(response.body) | |
end | |
def create_new_container(container) | |
Container.new(container["Id"], container["Names"].map { |name| name.gsub(/\A\//, '') }) | |
end | |
def build_container_list(containers) | |
containers.map { |container| create_new_container(container) } | |
end | |
def check_for_new_containers(new_containers) | |
new_containers.each do |new_container| | |
unless @containers.any? { |existing_container| existing_container.cid == new_container.cid } | |
@containers << new_container | |
end | |
end | |
end | |
def check_for_shutdown_containers(new_containers) | |
@containers.each do |existing_container| | |
unless new_containers.any? { |new_container| new_container.cid == existing_container.cid } | |
@containers.delete(existing_container) | |
end | |
end | |
end | |
def refresh_containers | |
if @containers.empty? | |
@containers = build_container_list(get_containers) | |
else | |
new_containers = build_container_list(get_containers) | |
check_for_new_containers(new_containers) | |
check_for_shutdown_containers(new_containers) | |
end | |
end | |
end | |
c = ContainerMonitor.new | |
loop do | |
sleep 60 | |
c.send_stats | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment