From 8d75865a5ce034c41d41612d0d3c1dec80f1118f Mon Sep 17 00:00:00 2001 From: Rick Sherman Date: Tue, 10 May 2016 12:47:01 -0500 Subject: [PATCH] (RE-7014) Add support for statsd They way we were using graphite was incorrect for the type of data we were sending it. statsd is the appropriate mechanism for our needs. statsd and graphite are mutually exclusive and configuring statsd will take precendence over Graphite. Example of configuration in vmpooler.yaml.example --- Gemfile | 2 + lib/vmpooler.rb | 16 ++++++++ lib/vmpooler/pool_manager.rb | 15 ++++++-- lib/vmpooler/statsd.rb | 12 ++++++ spec/spec_helper.rb | 4 ++ spec/vmpooler/pool_manager_spec.rb | 62 +++++++++++++++++++++++++++++- vmpooler | 8 +++- vmpooler.yaml.example | 31 ++++++++++++++- 8 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 lib/vmpooler/statsd.rb diff --git a/Gemfile b/Gemfile index c71be35..c6cecf1 100644 --- a/Gemfile +++ b/Gemfile @@ -7,10 +7,12 @@ gem 'rbvmomi', '>= 1.8' gem 'redis', '>= 3.2' gem 'sinatra', '>= 1.4' gem 'net-ldap', '<= 0.12.1' # keep compatibility w/ jruby & mri-1.9.3 +gem 'statsd-ruby', '>= 1.3.0' # Test deps group :test do gem 'rack-test', '>= 0.6' gem 'rspec', '>= 3.2' + gem 'simplecov', '>= 0.11.2' gem 'yarjuf', '>= 2.0' end diff --git a/lib/vmpooler.rb b/lib/vmpooler.rb index 70bb819..0e3fffa 100644 --- a/lib/vmpooler.rb +++ b/lib/vmpooler.rb @@ -7,6 +7,7 @@ module Vmpooler require 'rbvmomi' require 'redis' require 'sinatra/base' + require "statsd-ruby" require 'time' require 'timeout' require 'yaml' @@ -52,6 +53,13 @@ module Vmpooler parsed_config[:graphite]['prefix'] ||= 'vmpooler' end + # statsd is an addition and my not be present in YAML configuration + if parsed_config[:statsd] + if parsed_config[:statsd]['server'] + parsed_config[:statsd]['prefix'] ||= 'vmpooler' + end + end + if parsed_config[:tagfilter] parsed_config[:tagfilter].keys.each do |tag| parsed_config[:tagfilter][tag] = Regexp.new(parsed_config[:tagfilter][tag]) @@ -79,6 +87,14 @@ module Vmpooler end end + def self.new_statsd(server, port) + if server.nil? or server.empty? or server.length == 0 + nil + else + Statsd.new server, port + end + end + def self.pools(conf) conf[:pools] end diff --git a/lib/vmpooler/pool_manager.rb b/lib/vmpooler/pool_manager.rb index 1a0454c..d309147 100644 --- a/lib/vmpooler/pool_manager.rb +++ b/lib/vmpooler/pool_manager.rb @@ -1,12 +1,17 @@ module Vmpooler class PoolManager - def initialize(config, logger, redis, graphite=nil) + def initialize(config, logger, redis, graphite=nil, statsd=nil) $config = config # Load logger library $logger = logger - unless graphite.nil? + # statsd and graphite are mutex in the context of vmpooler + unless statsd.nil? + $statsd = statsd + end + + unless graphite.nil? || !statsd.nil? $graphite = graphite end @@ -258,6 +263,7 @@ module Vmpooler $redis.decr('vmpooler__tasks__clone') begin + $statsd.timing($config[:statsd]['prefix'] + ".clone.#{vm['template']}", finish) if defined? $statsd $graphite.log($config[:graphite]['prefix'] + ".clone.#{vm['template']}", finish) if defined? $graphite rescue end @@ -565,7 +571,10 @@ module Vmpooler total = $redis.scard('vmpooler__pending__' + pool['name']) + ready begin - if defined? $graphite + if defined? $statsd + $statsd.increment($config[:statsd]['prefix'] + '.ready.' + pool['name'], $redis.scard('vmpooler__ready__' + pool['name'])) + $statsd.increment($config[:statsd]['prefix'] + '.running.' + pool['name'], $redis.scard('vmpooler__running__' + pool['name'])) + elsif defined? $graphite $graphite.log($config[:graphite]['prefix'] + '.ready.' + pool['name'], $redis.scard('vmpooler__ready__' + pool['name'])) $graphite.log($config[:graphite]['prefix'] + '.running.' + pool['name'], $redis.scard('vmpooler__running__' + pool['name'])) end diff --git a/lib/vmpooler/statsd.rb b/lib/vmpooler/statsd.rb new file mode 100644 index 0000000..7995fb5 --- /dev/null +++ b/lib/vmpooler/statsd.rb @@ -0,0 +1,12 @@ +require 'rubygems' unless defined?(Gem) + +module Vmpooler + class Statsd + def initialize( + s = 'statsd', + port = 8125 + ) + @server = Statsd.new s, port + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2186bdb..f4fbf55 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,7 @@ +require 'simplecov' +SimpleCov.start do + add_filter '/spec/' +end require 'helpers' require 'rbvmomi' require 'rspec' diff --git a/spec/vmpooler/pool_manager_spec.rb b/spec/vmpooler/pool_manager_spec.rb index a402bf4..b314ec3 100644 --- a/spec/vmpooler/pool_manager_spec.rb +++ b/spec/vmpooler/pool_manager_spec.rb @@ -5,13 +5,12 @@ describe 'Pool Manager' do let(:logger) { double('logger') } let(:redis) { double('redis') } let(:config) { {} } - let(:graphite) { nil } let(:pool) { 'pool1' } let(:vm) { 'vm1' } let(:timeout) { 5 } let(:host) { double('host') } - subject { Vmpooler::PoolManager.new(config, logger, redis, graphite) } + subject { Vmpooler::PoolManager.new(config, logger, redis) } describe '#_check_pending_vm' do let(:pool_helper) { double('pool') } @@ -252,6 +251,65 @@ describe 'Pool Manager' do end end + describe '#_stats_running_ready' do + let(:pool_helper) { double('pool') } + let(:vsphere) { {pool => pool_helper} } + let(:graphite) { double('graphite') } + let(:config) { { + config: { task_limit: 10 }, + pools: [ {'name' => 'pool1', 'size' => 5} ], + graphite: { 'prefix' => 'vmpooler' } + } } + + before do + expect(subject).not_to be_nil + $vsphere = vsphere + allow(logger).to receive(:log) + allow(pool_helper).to receive(:find_folder) + allow(redis).to receive(:smembers).and_return([]) + allow(redis).to receive(:set) + allow(redis).to receive(:get).with('vmpooler__tasks__clone').and_return(0) + allow(redis).to receive(:get).with('vmpooler__empty__pool1').and_return(nil) + end + + context 'graphite' do + let(:graphite) { double('graphite') } + subject { Vmpooler::PoolManager.new(config, logger, redis, graphite) } + + it 'increments graphite when enabled and statsd disabled' do + allow(redis).to receive(:scard).with('vmpooler__ready__pool1').and_return(1) + allow(redis).to receive(:scard).with('vmpooler__cloning__pool1').and_return(0) + allow(redis).to receive(:scard).with('vmpooler__pending__pool1').and_return(0) + allow(redis).to receive(:scard).with('vmpooler__running__pool1').and_return(5) + + expect(graphite).to receive(:log).with('vmpooler.ready.pool1', 1) + expect(graphite).to receive(:log).with('vmpooler.running.pool1', 5) + subject._check_pool(config[:pools][0]) + end + end + + context 'statsd' do + let(:statsd) { double('statsd') } + let(:config) { { + config: { task_limit: 10 }, + pools: [ {'name' => 'pool1', 'size' => 5} ], + statsd: { 'prefix' => 'vmpooler' } + } } + subject { Vmpooler::PoolManager.new(config, logger, redis, graphite, statsd) } + + it 'increments statsd when configured' do + allow(redis).to receive(:scard).with('vmpooler__ready__pool1').and_return(1) + allow(redis).to receive(:scard).with('vmpooler__cloning__pool1').and_return(0) + allow(redis).to receive(:scard).with('vmpooler__pending__pool1').and_return(0) + allow(redis).to receive(:scard).with('vmpooler__running__pool1').and_return(5) + + expect(statsd).to receive(:increment).with('vmpooler.ready.pool1', 1) + expect(statsd).to receive(:increment).with('vmpooler.running.pool1', 5) + subject._check_pool(config[:pools][0]) + end + end + end + describe '#_create_vm_snapshot' do let(:snapshot_manager) { 'snapshot_manager' } let(:pool_helper) { double('snapshot_manager') } diff --git a/vmpooler b/vmpooler index 5d0dd51..96b46a1 100755 --- a/vmpooler +++ b/vmpooler @@ -9,6 +9,11 @@ config = Vmpooler.config redis_host = config[:redis]['server'] logger_file = config[:config]['logfile'] graphite = config[:graphite]['server'] ? config[:graphite]['server'] : nil +# statsd is an addition and my not be present in YAML configuration +if config[:statsd] + statsd = config[:statsd]['server'] ? config[:statsd]['server'] : nil + statsd_port = config[:statsd]['port'] ? config[:statsd]['port'] : 8125 +end api = Thread.new { thr = Vmpooler::API.new @@ -21,7 +26,8 @@ manager = Thread.new { config, Vmpooler.new_logger(logger_file), Vmpooler.new_redis(redis_host), - Vmpooler.new_graphite(graphite) + Vmpooler.new_graphite(graphite), + Vmpooler.new_statsd(statsd, statsd_port) ).execute! } diff --git a/vmpooler.yaml.example b/vmpooler.yaml.example index 26f2c51..5476094 100644 --- a/vmpooler.yaml.example +++ b/vmpooler.yaml.example @@ -53,10 +53,39 @@ :redis: server: 'redis.company.com' + + # :statsd: + # + # This section contains the connection information required to store + # historical data via statsd. This is mutually exclusive with graphite + # and takes precedence. + # + # Available configuration parameters: + # + # - server + # The FQDN hostname of the statsd daemon. + # (optional) + # + # - prefix + # The prefix to use while storing statsd data. + # (optional; default: 'vmpooler') + # + # - port + # The UDP port to communicate with statsd daemon. + # (optional; default: 8125) + + # Example: + + :statsd: + server: 'statsd.company.com' + prefix: 'vmpooler' + port: 8125 + # :graphite: # # This section contains the connection information required to store -# historical data in an external Graphite database. +# historical data in an external Graphite database. This is mutually exclusive +# with statsd. # # Available configuration parameters: #