Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
37ee292
Added gauges in addition to timers and counters
Ulrhol Sep 27, 2014
660462c
Made the processctl work on ruby 1.9.x
Ulrhol Sep 27, 2014
8b3cfb8
Created a gemspec file
Ulrhol Sep 27, 2014
23139fd
Added dynamically generated stuff to gitignore
Ulrhol Sep 27, 2014
963eac2
Moved rstatsd.rb to bin/rstatsd
Ulrhol Sep 27, 2014
5acd2c2
Created an upstart job for rstatsd
Ulrhol Sep 27, 2014
34b5c49
Made the gemspec use git ls-files to make sure all files got picked-up
Ulrhol Sep 27, 2014
51bf7f6
Start use the gem provided rstatsd instead
Ulrhol Sep 27, 2014
662b96f
Removed the license, as no official license information could be foun…
Ulrhol Sep 27, 2014
96f52c5
Moved the example conf to config dir
Ulrhol Sep 27, 2014
6f142ab
include only config, bin and lib dir from git ls-files
Ulrhol Sep 27, 2014
2faddff
Added tool to assist in testing regexps for matching log messages
Ulrhol Sep 27, 2014
a301bcd
Added support for conf.d glob includes
Ulrhol Oct 22, 2014
9ec44fc
Updated the conf.d file globbing support to merge in a more sophistic…
Ulrhol Oct 22, 2014
90af59c
glob include conf parse fix
tephlon Nov 8, 2014
eb75f4f
Added time interval support
tephlon Nov 8, 2014
dc138df
Added support for "unmetrics"
tephlon Nov 8, 2014
ed0fddc
Fix for nested captures
tephlon Nov 8, 2014
2cba4e9
Added *.gem to avoid checking in the gem files
Ulrhol Nov 9, 2014
2ad6612
Bumped the gemspec version
Ulrhol Nov 9, 2014
ab39dd6
Merge remote-tracking branch 'origin/feature_intervals_and_unmetrics'…
Ulrhol Nov 9, 2014
1e2ea94
Bumped the gem version
Ulrhol Nov 9, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
rstatsd.yaml
rstatsd-cfg.rb
*.gem
rstatsd-1.0.0.tar.gz
rstatsd-1.0.0/
ruby-rstatsd-1.0.0/
ruby-rstatsd_1.0.0-1.debian.tar.gz
ruby-rstatsd_1.0.0-1.dsc
ruby-rstatsd_1.0.0-1_all.deb
ruby-rstatsd_1.0.0-1_amd64.changes
ruby-rstatsd_1.0.0.orig.tar.gz
15 changes: 14 additions & 1 deletion rstatsd.rb → bin/rstatsd
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
require 'optparse'
require 'rubygems'
require 'statsd'
require File.dirname(__FILE__)+'/lib/rstatsd'
require 'rstatsd'


def setup_logger ( logfile, loglevel = "INFO" )
Expand All @@ -27,6 +27,7 @@ def setup_logger ( logfile, loglevel = "INFO" )
options = { :cfg_file => File.dirname(__FILE__)+'/rstatsd.yaml',
:pidfile => '/tmp/rstatsd.pid',
:ctl_cmd => "start",
:cfg_dir => File.dirname(__FILE__)+'/conf.d',
:daemonize => false }


Expand All @@ -38,6 +39,9 @@ def setup_logger ( logfile, loglevel = "INFO" )
opts.on("-c", "--config FILE", String, "Configuration file (default #{options[:cfg_file]})") do |v|
options[:cfg_file] = v
end
opts.on("-g", "--glob-dir FILE", String, "Configuration include directory (default #{options[:cfg_dir]})") do |v|
options[:cfg_dir] = v
end
opts.on("-p", "--pidfile FILE", String, "PID file (default #{options[:pidfile]})") do |v|
options[:pidfile] = v
end
Expand All @@ -50,7 +54,15 @@ def setup_logger ( logfile, loglevel = "INFO" )
end
end.parse!

puts "Loading config from " + options[:cfg_file]
cfg = YAML.load_file(options[:cfg_file])
cfg[:cmds] ||= []

Dir[options[:cfg_dir]+'/*.yaml'].each do |file|
include_cfg = YAML.load_file(file)
puts "Loading " + file
cfg[:cmds] = cfg[:cmds].push(include_cfg[0])
end

Statsd.host = cfg[:statsd][:host]
Statsd.port = cfg[:statsd][:port]
Expand All @@ -61,6 +73,7 @@ def setup_logger ( logfile, loglevel = "INFO" )

cfg[:cmds].each do |cmd|
params = { :every => cmd[:every],
:interval => cmd[:interval],
:logger => logger,
:prefix => cfg[:metric_prefix] }

Expand Down
File renamed without changes.
13 changes: 13 additions & 0 deletions config/rstatsd.upstart.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
description "Capture metrics from logs and send to statsd server"
start on runlevel [2345]
stop on runlevel [!2345]

respawn
expect fork
kill timeout 2

pre-start exec /usr/bin/test -e /etc/rstatsd.yaml

exec /usr/bin/rstatsd -c /etc/rstatsd.yaml -d -k start

pre-stop exec /usr/bin/rstatsd -c /etc/rstatsd.yaml -d -k stop
21 changes: 21 additions & 0 deletions config/test_regex.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/perl

# example:
# perl test_regex.pl "Charlie has 9 girls in 12 cities" "Charlie\s+has\s+(?<girls>\d+)\s+girls\s+in\s+(?<cities>\d+)\s+cities"
#
# Should return:
# girls: 9
# cities: 12

# Parse command line arguments
$input_string = $ARGV[0];
$input_regexp = $ARGV[1];

# Perform regexp matching
$input_string =~ m/($input_regexp)/;

# Print the resulting groups and values
while (($key, $value) = each(%+)){
print $key.": ".$value."\n";
}

2 changes: 1 addition & 1 deletion lib/rstatsd/processctl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def get_running_pids
end

def get_allpids
@allpids = `ps -ef |sed 1d`.to_a.map { |x| a = x.strip.split(/\s+/); [a[1].to_i,a[2].to_i] }
@allpids = `ps -ef |sed 1d`.lines.to_a.map { |x| a = x.strip.split(/\s+/); [a[1].to_i,a[2].to_i] }
end

# thar be recursion ahead, matey
Expand Down
76 changes: 56 additions & 20 deletions lib/rstatsd/rstatsd.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
module RStatsd
ENV['TZ'] = ":/etc/localtime"

module Helpers
def prefix_metric_name ( pieces )
pieces.join(".")
Expand All @@ -11,13 +13,15 @@ def logit ( msg, level = Logger::INFO )

class Command
include Helpers
attr_accessor :command, :every
attr_accessor :command
def initialize ( h = {}, &block )
@command = nil
@logger = h[:logger]
@every = h[:every] || 1
@prefix = h[:prefix]
@regexes = []
@command = nil
@logger = h[:logger]
@every = h[:every] || 1
@interval = h[:interval] || nil
@prefix = h[:prefix]
@regexes = []

yield self
end

Expand All @@ -30,26 +34,41 @@ def execute!
trap(:INT) { logit("Caught SIGINT. Exiting"); Process.kill(:INT, pipe.pid) }
trap(:TERM) { logit("Caught SIGTERM. Exiting"); Process.kill(:TERM, pipe.pid) }

timers, counters = {}, {}
timers, counters, gauges = {}, {}, {}

logit("Starting thread for command #{@command}")
logit("Starting thread for command #{@command}; interval #{@interval}, every #{@every}")
pipe = IO.popen(@command)
begin
if @interval
next_update = Time.now.to_f + @interval
end

pipe.each_with_index do |l,i|
@regexes.each do |r|
if r.statsd_type == RegExData::Timer
timers = r.get_increments(l, timers)
elsif r.statsd_type == RegExData::Gauge
gauges = r.get_increments(l, gauges)
else
counters = r.get_increments(l, counters)
end
end

# only send to statsd every x lines - this is to avoid UDP floods
if (i % @every) == 0
logit("#{i} lines, sending to statsd", Logger::DEBUG)
if @interval
do_update = Time.now.to_f >= next_update
else
do_update = (i % @every) == 0
end

if do_update
logit("#{@command}: #{i} lines, sending to statsd", Logger::DEBUG)
statsd_send(counters)
statsd_send(gauges, 'gauge')
statsd_send(divide_hash(timers, @every), 'timer')
timers, counters = {}, {}
timers, counters, gauges = {}, {}, {}
if @interval
next_update = Time.now.to_f + @interval
end
end
# this is for debugging
#sleep(rand/100)
Expand All @@ -73,6 +92,9 @@ def statsd_send ( h, statsd_type = nil )
if statsd_type == 'timer'
Statsd.timing(k,v)
logit("Set timer value #{k} to #{v}",Logger::DEBUG)
elsif statsd_type == 'gauge'
Statsd.gauge(k,v)
logit("Set gauge value #{k} to #{v}",Logger::DEBUG)
else
Statsd.update_counter(k,v)
logit("Incremented #{k} by #{v}",Logger::DEBUG)
Expand All @@ -87,13 +109,15 @@ class RegExData
attr_writer :logger
attr_reader :regex, :statsd_type

Counter, Timer = 1, 2
Counter, Timer, Gauge = 1, 2, 3

def initialize ( h )
@regex = which_regex h[:regex]
@metrics = h[:metrics] || []
@unmetrics = h[:unmetrics] || []
@statsd_type = case h[:statsd_type]
when 'timer' then Timer
when 'gauge' then Gauge
else Counter
end
@statsd = h[:statsd] || true
Expand All @@ -110,13 +134,23 @@ def initialize ( h )
def get_increments ( line, h )
h ||= {}
@matches = @regex.match(line)
return h unless @matches
if has_captures?
@matches.names.each do |name|
h = build_and_increment(h, name )
if @matches
if has_captures?
@matches.names.each do |name|
# If we use nested captures, we can get empty MatchData members,
# we should skip those.
if !@matches[name.to_sym].nil?
h = build_and_increment(h, name )
end
end
else
h = build_and_increment(h)
end
elsif @unmetrics
@unmetrics.each do |unmetric|
h[unmetric] ||= 0
h[unmetric] += 1
end
else
h = build_and_increment(h)
end
h
end
Expand All @@ -132,11 +166,13 @@ def build_and_increment ( h, name = nil )
h[metric_name] ||= 0
if @statsd_type == Timer
h[metric_name] += @matches[name.to_sym].to_f
elsif @statsd_type == Gauge
h[metric_name] = @matches[name.to_sym].to_f
else
h[metric_name] += @matches[name.to_sym].to_i
end
else
# the value of the named capture will be used as a leaf node in the mtric name
# the value of the named capture will be used as a leaf node in the metric name
metric_name = prefix_metric_name( [ metric_name, name, @matches[name.to_sym] ] ) if name
h[metric_name] ||= 0
h[metric_name] += 1
Expand Down
19 changes: 19 additions & 0 deletions rstatsd.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
$:.push File.expand_path("../lib", __FILE__)

Gem::Specification.new do |s|
s.name = 'rstatsd'
s.version = '1.0.3'
s.date = '2014-11-09'
s.summary = "Tool to turn logs into statsd metrics."
s.description = "rstatsd is a daemon that takes the output of multiple commands and sends
the output to statsd based on a regular expressions with optional named capture
groups."
s.authors = ["Aaron Brown", "Ulrik Holmen", "Stefan Bergstrom"]
s.email = '9minutesnooze@github.com'
s.files = `git ls-files -- bin lib config`.split("\n")
s.homepage = 'https://github.com/ideeli/rstatsd'
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.default_executable = 'rstatsd'
s.add_dependency 'statsd-client'
end