droid 0.9.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.
- data/bin/conf +31 -0
- data/lib/droid.rb +531 -0
- data/lib/droid/sync.rb +0 -0
- data/lib/heroku_droid.rb +86 -0
- data/lib/local_stats.rb +143 -0
- data/lib/memcache_cluster.rb +127 -0
- data/lib/stats.rb +30 -0
- data/lib/utilization.rb +90 -0
- data/speaker/droid_speaker.rb +79 -0
- data/speaker/speak +33 -0
- data/speaker/test.rb +58 -0
- data/test/base.rb +43 -0
- data/test/droid_test.rb +53 -0
- data/test/heroku_droid_test.rb +42 -0
- data/test/wait_for_port_test.rb +23 -0
- data/vendor/logger_client/Rakefile +53 -0
- data/vendor/logger_client/init.rb +1 -0
- data/vendor/logger_client/lib/logger_client.rb +210 -0
- data/vendor/logger_client/test.rb +18 -0
- metadata +115 -0
    
        data/lib/droid/sync.rb
    ADDED
    
    | 
            File without changes
         | 
    
        data/lib/heroku_droid.rb
    ADDED
    
    | @@ -0,0 +1,86 @@ | |
| 1 | 
            +
            require 'rubygems'
         | 
| 2 | 
            +
            require 'time'
         | 
| 3 | 
            +
            require 'uri'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require File.dirname(__FILE__) + '/droid'
         | 
| 6 | 
            +
            require File.dirname(__FILE__) + '/local_stats'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            require File.dirname(__FILE__) + '/../vendor/logger_client/init'
         | 
| 9 | 
            +
            Log.configure do |c|
         | 
| 10 | 
            +
            	slot = LocalStats.slot
         | 
| 11 | 
            +
            	slot = 'railgun' if slot == 'userapps'
         | 
| 12 | 
            +
            	c.component = slot
         | 
| 13 | 
            +
            	c.instance_name = LocalStats.this_instance_name
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            class HerokuDroid < Droid
         | 
| 17 | 
            +
            	attr_reader :extended_stats
         | 
| 18 | 
            +
            	attr_reader :force_syslog
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            	def initialize(name, options = {}, &block)
         | 
| 21 | 
            +
            		@name = name
         | 
| 22 | 
            +
            		@force_syslog = (options[:force_syslog] == true)
         | 
| 23 | 
            +
            		@extended_stats = !(options[:extended_stats] == false)
         | 
| 24 | 
            +
            		Droid.new(name, self.class.default_options) do |d|
         | 
| 25 | 
            +
            			standard_topics(d) unless options[:standard_topics] == false
         | 
| 26 | 
            +
            			LocalStats.attach
         | 
| 27 | 
            +
            			block.call(d)
         | 
| 28 | 
            +
            		end
         | 
| 29 | 
            +
            	end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            	def ruby_path
         | 
| 32 | 
            +
            		if File.exists?("/usr/ruby1.8.6/bin/ruby")
         | 
| 33 | 
            +
            			"/usr/ruby1.8.6/bin"
         | 
| 34 | 
            +
            		else
         | 
| 35 | 
            +
            			"/usr/local/bin"
         | 
| 36 | 
            +
            		end
         | 
| 37 | 
            +
            	end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            	def standard_topics(droid)
         | 
| 40 | 
            +
            		queue_name = Droid.gen_queue(droid, 'ping') + ".pid#{Process.pid}"
         | 
| 41 | 
            +
            		droid.listen4('ping', :queue => queue_name) do |msg|
         | 
| 42 | 
            +
            			Utilization.latency = (Time.now.to_f - msg['departed_at']).abs
         | 
| 43 | 
            +
            		end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            		blk = Proc.new do |d|
         | 
| 46 | 
            +
            			begin
         | 
| 47 | 
            +
            				t1 = Time.now
         | 
| 48 | 
            +
            				response = {}.merge(LocalStats.stats)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            				estats = nil
         | 
| 51 | 
            +
            				if self.extended_stats
         | 
| 52 | 
            +
            					estats = droid.call_stats
         | 
| 53 | 
            +
            					estats = { :notes => estats } unless estats.kind_of?(Hash)
         | 
| 54 | 
            +
            					estats[:notes] ||= estats.map { |k, v| "#{v} #{k}" }.join(", ")
         | 
| 55 | 
            +
            					estats.merge!(LocalStats.extended_stats)
         | 
| 56 | 
            +
            				end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            				response.merge!({
         | 
| 59 | 
            +
            					:extended_stats => estats,
         | 
| 60 | 
            +
            					:droid_name => name,
         | 
| 61 | 
            +
            					:utilization => Utilization.report,
         | 
| 62 | 
            +
            					:latency => Utilization.latency,
         | 
| 63 | 
            +
            				})
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            				d.publish('pong', response.merge(:stat_collection => (Time.now - t1)))
         | 
| 66 | 
            +
            			rescue Object => e
         | 
| 67 | 
            +
            				Log.error "Ping Block Error: #{e.class} -> #{e.message}", :exception => e
         | 
| 68 | 
            +
            				STDERR.puts e.backtrace.join("\n") + "\n"
         | 
| 69 | 
            +
            			end
         | 
| 70 | 
            +
            		end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            		blk.call(droid)
         | 
| 73 | 
            +
            		EM.add_periodic_timer(50 + (rand*15).to_i) { blk.call(droid) }
         | 
| 74 | 
            +
            		EM.add_periodic_timer(360) { Utilization.reinit }
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            		return unless LocalStats.slot == self.name.downcase or self.force_syslog
         | 
| 77 | 
            +
            	end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            	def self.publish_oneshot(topic, params={})
         | 
| 80 | 
            +
            		HerokuDroid.new('One-shot publish') do |droid|
         | 
| 81 | 
            +
            			droid.publish(topic, params)
         | 
| 82 | 
            +
            			HerokuDroid.stop_safe
         | 
| 83 | 
            +
            		end
         | 
| 84 | 
            +
            	end
         | 
| 85 | 
            +
            end
         | 
| 86 | 
            +
             | 
    
        data/lib/local_stats.rb
    ADDED
    
    | @@ -0,0 +1,143 @@ | |
| 1 | 
            +
            require 'rest_client'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module LocalStats
         | 
| 4 | 
            +
            	extend self
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            	def load_avg
         | 
| 7 | 
            +
            		File.read('/proc/loadavg').split(' ', 2).first.to_f
         | 
| 8 | 
            +
            	end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            	def memory_info
         | 
| 11 | 
            +
            		info = {}
         | 
| 12 | 
            +
            		File.read('/proc/meminfo').split("\n").each do |line|
         | 
| 13 | 
            +
            			name, value = line.split(/:\s+/, 2)
         | 
| 14 | 
            +
            			info[name] = value.to_i
         | 
| 15 | 
            +
            		end
         | 
| 16 | 
            +
            		info
         | 
| 17 | 
            +
            	end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            	def memory_use
         | 
| 20 | 
            +
            		info = memory_info
         | 
| 21 | 
            +
            		used = info['MemTotal'] - info['MemFree'] - info['Cached'] - info['Buffers']
         | 
| 22 | 
            +
            		(100 * used / info['MemTotal']).to_i
         | 
| 23 | 
            +
            	end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            	def swap_use
         | 
| 26 | 
            +
            		_, total, use, _ = `free | grep Swap:`.strip.split
         | 
| 27 | 
            +
            		return 0 if total.to_i == 0
         | 
| 28 | 
            +
            		(100 * use.to_i / total.to_i).to_i
         | 
| 29 | 
            +
            	end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            	def local_ip
         | 
| 32 | 
            +
            		@local_ip ||= fetch_local_ip
         | 
| 33 | 
            +
            	end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            	def fetch_local_ip
         | 
| 36 | 
            +
            		`/sbin/ifconfig eth0 | grep inet | awk '{print $2}'`.gsub('addr:', '').strip
         | 
| 37 | 
            +
            	end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            	def public_ip
         | 
| 40 | 
            +
            		retries = 0
         | 
| 41 | 
            +
            		@public_ip ||= begin
         | 
| 42 | 
            +
            			RestClient.get("http://169.254.169.254/latest/meta-data/public-ipv4").strip
         | 
| 43 | 
            +
            		rescue RestClient::RequestTimeout => e
         | 
| 44 | 
            +
            			retries += 1
         | 
| 45 | 
            +
            			raise if retries > 5
         | 
| 46 | 
            +
            			sleep 1
         | 
| 47 | 
            +
            			retry
         | 
| 48 | 
            +
            		end
         | 
| 49 | 
            +
            	end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            	def disk_stats
         | 
| 52 | 
            +
            		raw = `df -P | grep -v tmpfs | grep -v udev | grep -v slugs | awk '{print $1 " ## " $6 " ##  " $5}'`.split("\n").collect { |d| d }
         | 
| 53 | 
            +
            		raw.shift
         | 
| 54 | 
            +
            		raw.collect do |d|
         | 
| 55 | 
            +
            			tokens = d.split("##").collect { |t| t.strip }
         | 
| 56 | 
            +
            			{
         | 
| 57 | 
            +
            				:device => tokens[0],
         | 
| 58 | 
            +
            				:path => tokens[1],
         | 
| 59 | 
            +
            				:usage => tokens[2].gsub('%','').to_i
         | 
| 60 | 
            +
            			}
         | 
| 61 | 
            +
            		end
         | 
| 62 | 
            +
            	end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            	def ion_instance_id
         | 
| 65 | 
            +
            		@ion_instnace_id ||= File.read('/etc/heroku/ion_instance_id').strip rescue nil
         | 
| 66 | 
            +
            	end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            	def slot
         | 
| 69 | 
            +
            		@slot ||= File.read('/etc/heroku/slot').strip.gsub(/64$/,'').split('-').first rescue nil
         | 
| 70 | 
            +
            	end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            	def this_instance_name
         | 
| 73 | 
            +
            		return nil if slot.nil? or ion_instance_id.nil?
         | 
| 74 | 
            +
            		"#{slot}.#{ion_instance_id}"
         | 
| 75 | 
            +
            	end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            	def stats
         | 
| 78 | 
            +
            		{
         | 
| 79 | 
            +
            			:slot            => slot,
         | 
| 80 | 
            +
            			:ion_id          => ion_instance_id,
         | 
| 81 | 
            +
            			:load_avg        => load_avg,
         | 
| 82 | 
            +
            			:memory_use      => memory_use,
         | 
| 83 | 
            +
            			:swap_use        => swap_use,
         | 
| 84 | 
            +
            			:local_ip        => local_ip,
         | 
| 85 | 
            +
            			:instance_name   => this_instance_name,
         | 
| 86 | 
            +
            			:public_ip       => public_ip,
         | 
| 87 | 
            +
            			:net_in_rate     => receive_rate,
         | 
| 88 | 
            +
            			:net_out_rate    => transmit_rate
         | 
| 89 | 
            +
            		}
         | 
| 90 | 
            +
            	end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
            	def extended_stats
         | 
| 93 | 
            +
            		{
         | 
| 94 | 
            +
            			:disk_stats => disk_stats
         | 
| 95 | 
            +
            		}
         | 
| 96 | 
            +
            	end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            	# sample ifstat every 30 seconds
         | 
| 99 | 
            +
            	IFSTAT_INTERVAL = 30
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            	# bytes received per second and bytes transmitted per
         | 
| 102 | 
            +
            	# second as a two tuple
         | 
| 103 | 
            +
            	def transfer_rates
         | 
| 104 | 
            +
            		[receive_rate, transmit_rate]
         | 
| 105 | 
            +
            	end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            	# bytes received per second
         | 
| 108 | 
            +
            	def receive_rate
         | 
| 109 | 
            +
            		@rxrate || 0
         | 
| 110 | 
            +
            	end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            	# bytes transmitted per second
         | 
| 113 | 
            +
            	def transmit_rate
         | 
| 114 | 
            +
            		@txrate || 0
         | 
| 115 | 
            +
            	end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            	# sample the current RX and TX bytes from ifconfig and
         | 
| 118 | 
            +
            	# return as a two-tuple.
         | 
| 119 | 
            +
            	def sample
         | 
| 120 | 
            +
            		data = ifconfig.match(/RX bytes:(\d+).*TX bytes:(\d+)/)
         | 
| 121 | 
            +
            		[data[1].to_i, data[2].to_i]
         | 
| 122 | 
            +
            	rescue => boom
         | 
| 123 | 
            +
            		Log.notice "error sampling network rate: #{boom.class} #{boom.message}"
         | 
| 124 | 
            +
            	end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            	def update_counters
         | 
| 127 | 
            +
            		rx, tx = sample
         | 
| 128 | 
            +
            		@rxrate = (rx - @rx) / IFSTAT_INTERVAL if @rx
         | 
| 129 | 
            +
            		@txrate = (tx - @tx) / IFSTAT_INTERVAL if @tx
         | 
| 130 | 
            +
            		@rx, @tx = rx, tx
         | 
| 131 | 
            +
            	end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
            	# called when a droid starts up - setup timers and whatnot
         | 
| 134 | 
            +
            	def attach
         | 
| 135 | 
            +
            		@rx, @tx, @rxrate, @txrate = nil
         | 
| 136 | 
            +
            		update_counters
         | 
| 137 | 
            +
            		EM.add_periodic_timer(IFSTAT_INTERVAL) { update_counters }
         | 
| 138 | 
            +
            	end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
            	def ifconfig
         | 
| 141 | 
            +
            		`/sbin/ifconfig eth0`
         | 
| 142 | 
            +
            	end
         | 
| 143 | 
            +
            end
         | 
| @@ -0,0 +1,127 @@ | |
| 1 | 
            +
            require 'memcache'
         | 
| 2 | 
            +
            require 'yaml'
         | 
| 3 | 
            +
            require 'digest/sha1'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # Manages a pool of memcache servers. This class should not be called
         | 
| 6 | 
            +
            # outside of the reactor - it does not account for asynchronous access
         | 
| 7 | 
            +
            # to the server list.
         | 
| 8 | 
            +
            module MemcacheCluster
         | 
| 9 | 
            +
            	extend self
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            	# A MemCache object configured with heroku's internal memcache namespace.
         | 
| 12 | 
            +
            	def heroku
         | 
| 13 | 
            +
            		cache('0Xfa15837Z')
         | 
| 14 | 
            +
            	end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            	def cache_retry(prefix, opts={})
         | 
| 17 | 
            +
            		opts[:retries] ||= 5
         | 
| 18 | 
            +
            		opts[:delay] ||= 0.5
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            		retried = 0
         | 
| 21 | 
            +
            		begin
         | 
| 22 | 
            +
            			c = cache(prefix)
         | 
| 23 | 
            +
            			yield c if block_given?
         | 
| 24 | 
            +
            		rescue MemCache::MemCacheError => e
         | 
| 25 | 
            +
            			Log.error "#{e.class} -> #{e.message}", :exception => e
         | 
| 26 | 
            +
            			raise if retried > opts[:retries]
         | 
| 27 | 
            +
            			retried += 1
         | 
| 28 | 
            +
            			sleep opts[:delay]
         | 
| 29 | 
            +
            			@caches = { }
         | 
| 30 | 
            +
            			retry
         | 
| 31 | 
            +
            		end
         | 
| 32 | 
            +
            	end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            	def set(prefix, *args)
         | 
| 35 | 
            +
            		res = nil
         | 
| 36 | 
            +
            		cache_retry(prefix) do |c|
         | 
| 37 | 
            +
            			res = c.set(*args)
         | 
| 38 | 
            +
            		end
         | 
| 39 | 
            +
            		res
         | 
| 40 | 
            +
            	end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            	def get(prefix, *args)
         | 
| 43 | 
            +
            		res = nil
         | 
| 44 | 
            +
            		cache_retry(prefix) do |c|
         | 
| 45 | 
            +
            			res = c.get(*args)
         | 
| 46 | 
            +
            		end
         | 
| 47 | 
            +
            		res
         | 
| 48 | 
            +
            	end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            	# Create listeners for standard memcache cluster related topics.
         | 
| 51 | 
            +
            	def attach(droid, file='memcached.yml')
         | 
| 52 | 
            +
            		load_from_file(file)
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            		droid.listen4('memcache.up', :queue => "memcache.up.#{LocalStats.this_instance_name}.#$$") { |msg| add(msg['address'], msg['port']) }
         | 
| 55 | 
            +
            		droid.listen4('instance.down', :queue => "instance.down.#{LocalStats.this_instance_name}.#$$") { |msg| remove(msg['local_ip']) if msg['slot'] == 'memcache' }
         | 
| 56 | 
            +
            		EM.add_timer(1) { droid.publish('memcache.needed', {}) }
         | 
| 57 | 
            +
            	end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            	# A MemCache object configured with the given prefix.
         | 
| 60 | 
            +
            	def cache(prefix, options={})
         | 
| 61 | 
            +
            		caches[prefix] ||=
         | 
| 62 | 
            +
            			MemCache.new(servers, options.merge(:namespace => prefix))
         | 
| 63 | 
            +
            	end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            	alias_method :[], :cache
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            	def caches
         | 
| 68 | 
            +
            		reload_if_stale
         | 
| 69 | 
            +
            		@caches ||= {}
         | 
| 70 | 
            +
            	end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            	def servers
         | 
| 73 | 
            +
            		reload_if_stale
         | 
| 74 | 
            +
            		@servers ||= []
         | 
| 75 | 
            +
            	end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            	def add(ip, port)
         | 
| 78 | 
            +
            		host = [ip, port].join(':')
         | 
| 79 | 
            +
            		return if servers.include?(host)
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            		log { "#{host} added" }
         | 
| 82 | 
            +
            		@servers.push host
         | 
| 83 | 
            +
            		@servers.sort!
         | 
| 84 | 
            +
            		@caches = {}
         | 
| 85 | 
            +
            		write_to_file
         | 
| 86 | 
            +
            		@last_read = Time.now
         | 
| 87 | 
            +
            	end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            	def remove(host)
         | 
| 90 | 
            +
            		if servers.reject!{ |s| s =~ /^#{host}/ }
         | 
| 91 | 
            +
            			log { "#{host} removed" }
         | 
| 92 | 
            +
            			caches.clear
         | 
| 93 | 
            +
            			write_to_file
         | 
| 94 | 
            +
            		end
         | 
| 95 | 
            +
            	end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            	def reload_if_stale
         | 
| 98 | 
            +
            		if @last_read &&
         | 
| 99 | 
            +
            			(Time.now - @last_read) > 5 &&
         | 
| 100 | 
            +
            			File.mtime(@file) > @last_read
         | 
| 101 | 
            +
            			log { "server list modified. reloading." }
         | 
| 102 | 
            +
            			load_from_file(@file)
         | 
| 103 | 
            +
            		end
         | 
| 104 | 
            +
            	rescue => boom
         | 
| 105 | 
            +
            		# ignore errors accessing/reading file.
         | 
| 106 | 
            +
            	end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            	def load_from_file(file)
         | 
| 109 | 
            +
            		@file = file
         | 
| 110 | 
            +
            		@last_read = Time.now
         | 
| 111 | 
            +
            		@servers = YAML.load(File.read(file)) rescue []
         | 
| 112 | 
            +
            		@caches = {}
         | 
| 113 | 
            +
            	end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            	def write_to_file
         | 
| 116 | 
            +
            		log { "writing server list: #{@file}" }
         | 
| 117 | 
            +
            		File.open(@file, 'w') do |f|
         | 
| 118 | 
            +
            			f.flock(File::LOCK_EX)
         | 
| 119 | 
            +
            			f.write YAML.dump(@servers)
         | 
| 120 | 
            +
            			f.flock(File::LOCK_UN)
         | 
| 121 | 
            +
            		end
         | 
| 122 | 
            +
            	end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
            	def log(type=:debug)
         | 
| 125 | 
            +
            		Log.send(type, "memcached: #{yield}")
         | 
| 126 | 
            +
            	end
         | 
| 127 | 
            +
            end
         | 
    
        data/lib/stats.rb
    ADDED
    
    | @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            module Stats
         | 
| 2 | 
            +
            	# The MemCache instance used to manipulate stats.
         | 
| 3 | 
            +
            	def cache
         | 
| 4 | 
            +
            		MemcacheCluster.cache("heroku:stats")
         | 
| 5 | 
            +
            	end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            	# Increment a stat counter. If the counter does not exist,
         | 
| 8 | 
            +
            	# yield to the block and use the result as the current counter
         | 
| 9 | 
            +
            	# value. With no block, the counter will be started at zero.
         | 
| 10 | 
            +
            	def increment(key, amount=1)
         | 
| 11 | 
            +
            		if (value = cache.incr(key, amount)).nil?
         | 
| 12 | 
            +
            			value = yield if block_given?
         | 
| 13 | 
            +
            			value = (value || 0) + amount
         | 
| 14 | 
            +
            			cache.add(key, value.to_s, 0, true)
         | 
| 15 | 
            +
            		end
         | 
| 16 | 
            +
            	rescue => boom
         | 
| 17 | 
            +
            		Log.default_error(boom)
         | 
| 18 | 
            +
            		nil
         | 
| 19 | 
            +
            	end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            	# Set the stat counter to a specific value.
         | 
| 22 | 
            +
            	def sample(key, value)
         | 
| 23 | 
            +
            		cache.set(key, value.to_s, 0, true)
         | 
| 24 | 
            +
            	rescue => boom
         | 
| 25 | 
            +
            		Log.default_error(boom)
         | 
| 26 | 
            +
            		nil
         | 
| 27 | 
            +
            	end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            	extend self
         | 
| 30 | 
            +
            end
         | 
    
        data/lib/utilization.rb
    ADDED
    
    | @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            module Utilization
         | 
| 2 | 
            +
            	extend self
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            	@latency = 0.0
         | 
| 5 | 
            +
            	def latency=(val); @latency = val; end
         | 
| 6 | 
            +
            	def latency; @latency; end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            	@@start = Time.now
         | 
| 9 | 
            +
            	@@data = { }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            	def reinit
         | 
| 12 | 
            +
            		@@start = Time.now
         | 
| 13 | 
            +
            		@@data = { }
         | 
| 14 | 
            +
            	end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            	def data(topic)
         | 
| 17 | 
            +
            		@@data[topic] ||= {
         | 
| 18 | 
            +
            			'msgs' => 0,
         | 
| 19 | 
            +
            			'time' => 0.0
         | 
| 20 | 
            +
            		}
         | 
| 21 | 
            +
            		@@data[topic]
         | 
| 22 | 
            +
            	end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            	def topics
         | 
| 25 | 
            +
            		@@data.keys
         | 
| 26 | 
            +
            	end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            	def record(topic, secs)
         | 
| 29 | 
            +
            		d = data(topic)
         | 
| 30 | 
            +
            		d['msgs'] += 1
         | 
| 31 | 
            +
            		d['time'] += secs
         | 
| 32 | 
            +
            	end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            	def calc_utilization(topic, t2=nil)
         | 
| 35 | 
            +
            		d = data(topic)
         | 
| 36 | 
            +
            		t1 = @@start
         | 
| 37 | 
            +
            		t2 ||= Time.now
         | 
| 38 | 
            +
            		secs = (t2 - t1)
         | 
| 39 | 
            +
            		secs = 1 if secs <= 0.0
         | 
| 40 | 
            +
            		if d['msgs'] == 0
         | 
| 41 | 
            +
            			avg = 0.0
         | 
| 42 | 
            +
            		else
         | 
| 43 | 
            +
            			avg = d['time'] / d['msgs']
         | 
| 44 | 
            +
            		end
         | 
| 45 | 
            +
            		utilization = d['time'] / secs
         | 
| 46 | 
            +
            		{
         | 
| 47 | 
            +
            			:avg => avg,
         | 
| 48 | 
            +
            			:secs => secs,
         | 
| 49 | 
            +
            			:utilization => utilization,
         | 
| 50 | 
            +
            			:msgs => d['msgs'],
         | 
| 51 | 
            +
            			:msgs_per_sec => d['msgs'] / secs
         | 
| 52 | 
            +
            		}
         | 
| 53 | 
            +
            	end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            	def monitor(topic, opts={})
         | 
| 56 | 
            +
            		topic = 'temporary' if opts[:temp]
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            		t1 = Time.now
         | 
| 59 | 
            +
            		begin
         | 
| 60 | 
            +
            			yield if block_given?
         | 
| 61 | 
            +
            		ensure
         | 
| 62 | 
            +
            			t2 = Time.now
         | 
| 63 | 
            +
            			record(topic, t2 - t1)
         | 
| 64 | 
            +
            		end
         | 
| 65 | 
            +
            	end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            	def report
         | 
| 68 | 
            +
            		data = {}
         | 
| 69 | 
            +
            		t2 = Time.now
         | 
| 70 | 
            +
            		topics.each do |topic|
         | 
| 71 | 
            +
            			data[topic] = calc_utilization(topic, t2)
         | 
| 72 | 
            +
            		end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            		summary = { :avg => 0.0, :utilization => 0.0, :msgs => 0, :msgs_per_sec => 0.0, :secs => 0.0 }
         | 
| 75 | 
            +
            		data.each do |topic, d|
         | 
| 76 | 
            +
            			summary[:utilization] += d[:utilization]
         | 
| 77 | 
            +
            			summary[:msgs] += d[:msgs]
         | 
| 78 | 
            +
            			summary[:msgs_per_sec] += d[:msgs_per_sec]
         | 
| 79 | 
            +
            			summary[:avg] += d[:avg]
         | 
| 80 | 
            +
            			summary[:secs] += d[:secs]
         | 
| 81 | 
            +
            		end
         | 
| 82 | 
            +
            		if data.size < 1
         | 
| 83 | 
            +
            			summary[:avg] = 0.0
         | 
| 84 | 
            +
            		else
         | 
| 85 | 
            +
            			summary[:avg] /= data.size
         | 
| 86 | 
            +
            		end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            		{ :summary => summary, :data => data }
         | 
| 89 | 
            +
            	end
         | 
| 90 | 
            +
            end
         |