diff --git a/bin/statsd b/bin/statsd new file mode 100755 index 0000000..add0efa --- /dev/null +++ b/bin/statsd @@ -0,0 +1,56 @@ +#!/usr/bin/env ruby + +$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') +require 'yaml' +require 'optparse' + +begin + ORIGINAL_ARGV = ARGV.dup + + options = {:graphite => true, :mongo => false} + + parser = OptionParser.new do |opts| + opts.banner = "Usage: statsd [options]" + + opts.separator "" + opts.separator "Options:" + + opts.on("-cCONFIG", "--config-file CONFIG", "Configuration file") do |x| + options[:config] = x + end + + opts.on("-m", "--mongo", "Flush and aggregate stats to MongoDB") do + options[:mongo] = true + options[:graphite] = false + end + + opts.on("-g", "--graphite", "Flush stats to Graphite") do + options[:graphite] = true + end + + opts.on("-h", "--help", "Show this message") do + puts opts + exit + end + + end + + parser.parse! + + # dispatch + if !options[:config] + puts parser.help + else + require 'statsd' + require 'statsd/server' + Statsd::Server::Daemon.new.run(options) + end +rescue Exception => e + if e.instance_of?(SystemExit) + raise + else + puts 'Uncaught exception' + puts e.message + puts e.backtrace.join("\n") + end +end \ No newline at end of file diff --git a/config.yml b/config.yml index 897939e..9bf252f 100644 --- a/config.yml +++ b/config.yml @@ -1,14 +1,17 @@ --- +bind: 127.0.0.1 +port: 8125 + # Flush interval should be your finest retention in seconds flush_interval: 10 # Graphite graphite_host: localhost -graphite_port: 8125 +graphite_port: 2003 # Mongo -mongo_host: statsd.example.com -mongo_database: statsdb +mongo_host: localhost +mongo_database: statsdb # If you change these, you need to delete the capped collections yourself! # Average mongo record size is 152 bytes diff --git a/lib/statsd/echos.rb b/lib/statsd/echos.rb index 2b82896..233aff4 100644 --- a/lib/statsd/echos.rb +++ b/lib/statsd/echos.rb @@ -16,6 +16,6 @@ def receive_data data end EventMachine::run { - EventMachine::start_server "127.0.0.1", 8125, EchoServer - puts 'running dummy graphite echo server on 8125' + EventMachine::start_server "127.0.0.1", 2003, EchoServer + puts 'running dummy graphite echo server on 2003' } \ No newline at end of file diff --git a/lib/statsd/graphite.rb b/lib/statsd/graphite.rb index 483a9ae..f3e0851 100644 --- a/lib/statsd/graphite.rb +++ b/lib/statsd/graphite.rb @@ -27,7 +27,7 @@ def receive_data(data) # end def flush_stats - print "#{Time.now} Flushing #{counters.count} counters and #{timers.count} timers to Graphite" + print "#{Time.now} Flushing #{counters.count} counters and #{timers.count} timers to Graphite." stat_string = '' time = ::Benchmark.realtime do ts = Time.now.to_i diff --git a/lib/statsd/server.rb b/lib/statsd/server.rb index a7490ad..95fda67 100644 --- a/lib/statsd/server.rb +++ b/lib/statsd/server.rb @@ -1,14 +1,18 @@ require 'eventmachine' +require 'yaml' +require 'erb' module Statsd module Server #< EM::Connection - Version = '0.0.4' + Version = '0.5.0' FLUSH_INTERVAL = 10 COUNTERS = {} TIMERS = {} + def post_init puts "statsd server started!" end + def self.get_and_clear_stats! counters = COUNTERS.dup timers = TIMERS.dup @@ -16,6 +20,7 @@ def self.get_and_clear_stats! TIMERS.clear [counters,timers] end + def receive_data(msg) msg.split("\n").each do |row| # puts row @@ -37,5 +42,66 @@ def receive_data(msg) end end end + + class Daemon + def run(options) + config = YAML::load(ERB.new(IO.read(options[:config])).result) + + if options[:mongo] + require 'statsd/mongo' + # Setup retention store + db = Mongo::Connection.new(config['mongo_host']).db(config['mongo_database']) + config['retentions'].each do |retention| + collection_name = retention['name'] + unless db.collection_names.include?(collection_name) + db.create_collection(collection_name, :capped => retention['capped'], :size => retention['cap_bytes']) + end + db.collection(collection_name).ensure_index([['ts', Mongo::ASCENDING]]) + end + Statsd::Mongo.hostname = config['mongo_host'] + Statsd::Mongo.database = config['mongo_database'] + Statsd::Mongo.retentions = config['retentions'] + Statsd::Mongo.flush_interval = config['flush_interval'] + end + + if options[:graphite] + require 'statsd/graphite' + end + + # Start the server + EventMachine::run do + EventMachine::open_datagram_socket(config['bind'], config['port'], Statsd::Server) + + # Periodically Flush + EventMachine::add_periodic_timer(config['flush_interval']) do + counters,timers = Statsd::Server.get_and_clear_stats! + + # Flush Adapters + if options[:mongo] + EM.defer { Statsd::Mongo.flush_stats(counters,timers) } + end + + if options[:graphite] + EventMachine.connect config['graphite_host'], config['graphite_port'], Statsd::Graphite do |conn| + conn.counters = counters + conn.timers = timers + conn.flush_interval = config['flush_interval'] + conn.flush_stats + end + end + + end + + end + + end + end end -end \ No newline at end of file +end + + + +require 'statsd/graphite' + + + diff --git a/statsd.gemspec b/statsd.gemspec index be6e9d2..8d4be3b 100644 --- a/statsd.gemspec +++ b/statsd.gemspec @@ -10,12 +10,13 @@ Gem::Specification.new do |s| s.email = ['quasor@me.com'] s.homepage = "http://github.com/quasor/statsd" s.summary = "Ruby version of statsd." - s.description = "Ruby version of statsd." + s.description = "A network daemon for aggregating statistics (counters and timers), rolling them up, then sending them to graphite or mongo." s.required_rubygems_version = ">= 1.3.6" - s.add_dependency "eventmachine", "~> 0.12.10" - s.add_dependency "mongo", "~> 1.2.0" + s.add_dependency "eventmachine", "~> 0.12.10" + s.add_dependency "mongo", "~> 1.2.0" + s.add_dependency "erubis", "~> 2.6.6" s.files = `git ls-files`.split("\n") s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact