statsdserver 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/bin/statsd CHANGED
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $: << File.join(File.dirname(__FILE__), "..", "lib")
3
4
 
4
5
  require "rubygems"
5
- require "bundler/setup"
6
6
 
7
+ require "daemons"
7
8
  require "parseconfig"
8
9
  require "statsdserver"
9
- require "statsdserver/output/stdout"
10
+ require "statsdserver/output/amqp"
10
11
  require "statsdserver/output/tcp"
12
+ require "statsdserver/output/stdout"
11
13
  require "sysexits"
12
14
 
13
15
  include Sysexits
@@ -28,7 +30,7 @@ end
28
30
 
29
31
  config = {}
30
32
  %w(daemonize inputs flush_interval outputs prefix percentile
31
- suffix).each do |key|
33
+ suffix preserve_counters).each do |key|
32
34
  config[key.to_sym] = config_file[key] if config_file[key]
33
35
  end
34
36
 
@@ -42,16 +44,26 @@ if config[:outputs].nil? || config[:outputs].empty?
42
44
  exit EX_DATAERR
43
45
  end
44
46
 
47
+ config[:inputs] = config[:inputs].split(/, */)
45
48
  input_config = {}
46
- config[:inputs].split(/, */).each do |input|
49
+ config[:inputs].each do |input|
47
50
  input_config[input] = config_file["input:#{input}"] || {}
48
51
  end
49
52
 
53
+ config[:outputs] = config[:outputs].split(/, */)
50
54
  output_config = {}
51
- config[:outputs].split(/, */).each do |output|
55
+ config[:outputs].each do |output|
52
56
  output_config[output] = config_file["output:#{output}"] || {}
53
57
  end
54
58
 
59
+ if config_file["daemonize"] == "true"
60
+ if config[:outputs].include?("stdout")
61
+ $stderr.puts "#{progname}: output stdout is not compatible with daemonize"
62
+ exit EX_DATAERR
63
+ end
64
+ Daemons.daemonize(:app_name => progname)
65
+ end
66
+
55
67
  EM.run do
56
68
  s = StatsdServer.new(config, input_config, output_config)
57
69
  s.run
data/lib/statsdserver.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "logger"
2
2
  require "statsdserver/input/udp"
3
3
  require "statsdserver/input/zeromq"
4
+ require "statsdserver/math"
4
5
  require "statsdserver/stats"
5
6
 
6
7
  # Hack because the latest amqp gem uses String#bytesize, and not everyone
@@ -26,7 +27,8 @@ class StatsdServer
26
27
  :port => 8125,
27
28
  :percentile => 90,
28
29
  :flush_interval => 30,
29
- :prefix => "stats"
30
+ :prefix => "stats",
31
+ :preserve_counters => "true",
30
32
  }.merge(opts)
31
33
  @input_config = input_config
32
34
  @output_config = output_config
@@ -140,60 +142,65 @@ class StatsdServer
140
142
  # end
141
143
  # end # def setup_amqp
142
144
 
145
+ private
146
+ def metric_name(name)
147
+ if @opts[:prefix] && !@opts[:prefix].empty?
148
+ prefix = @opts[:prefix] + "."
149
+ else
150
+ prefix = ""
151
+ end
152
+
153
+ if @opts[:suffix] && !@opts[:suffix].empty?
154
+ suffix = @opts[:suffix] + "."
155
+ else
156
+ suffix = ""
157
+ end
158
+
159
+ return [prefix, name, suffix].join("")
160
+ end
161
+
143
162
  public
144
163
  def carbon_update_str
145
164
  updates = []
146
165
  now = Time.now.to_i
147
166
 
148
167
  timers = {}
149
- @stats.timers.each do |k, v|
168
+ @stats.timers.keys.each do |k|
150
169
  timers[k] = @stats.timers.delete(k)
151
170
  end
152
171
 
153
172
  counters = {}
154
- @stats.counters.each do |k, v|
173
+ @stats.counters.keys.each do |k|
155
174
  counters[k] = @stats.counters.delete(k)
156
175
  end
157
176
 
158
- timers.each do |key, values|
159
- next if values.length == 0
160
- values.sort!
161
-
162
- # do some basic summarizing of our timer data
163
- min = values[0]
164
- max = values[-1]
165
- mean = min
166
- maxAtThreshold = min
167
- if values.length > 1
168
- threshold_index = ((100 - @opts[:percentile]) / 100.0) \
169
- * values.length
170
- threshold_count = values.length - threshold_index.round
171
- valid_values = values.slice(0, threshold_count)
172
- maxAtThreshold = valid_values[-1]
173
- sum = 0
174
- valid_values.each { |v| sum += v }
175
- mean = sum / valid_values.length
177
+ if @opts[:preserve_counters] == "true"
178
+ # Keep sending a 0 for counters (even if we don't get updates)
179
+ counters.keys.each do |k|
180
+ @stats.counters[k] ||= 0 # Keep sending a 0 if we don't get updates
176
181
  end
182
+ end
177
183
 
178
- prefix = @opts[:prefix] ? "#{@opts[:prefix]}." : ""
179
- suffix = @opts[:suffix] ? ".#{@opts[:suffix]}" : ""
180
- updates << "#{prefix}timers.#{key}.mean#{suffix} #{mean} #{now}"
181
- updates << "#{prefix}timers.#{key}.upper#{suffix} #{max} #{now}"
182
- updates << "#{prefix}timers.#{key}.upper_#{@opts[:percentile]}#{suffix} " \
183
- "#{maxAtThreshold} #{now}"
184
- updates << "#{prefix}timers.#{key}.lower#{suffix} #{min} #{now}"
185
- updates << "#{prefix}timers.#{key}.count#{suffix} #{values.length} #{now}"
184
+ timers.each do |key, values|
185
+ next if values.length == 0
186
+ summary = ::StatsdServer::Math.summarize(values, @opts)
187
+
188
+ updates << [metric_name("timers.#{key}.mean"),
189
+ summary[:mean], now].join(" ")
190
+ updates << [metric_name("timers.#{key}.upper"),
191
+ summary[:upper], now].join(" ")
192
+ updates << [metric_name("timers.#{key}.lower"),
193
+ summary[:lower], now].join(" ")
194
+ updates << [metric_name("timers.#{key}.count"),
195
+ values.length, now].join(" ")
196
+ updates << [metric_name("timers.#{key}.upper_#{@opts[:percentile]}"),
197
+ summary[:max_at_threshold], now].join(" ")
186
198
  end # timers.each
187
199
 
188
- # Keep sending a 0 for counters (even if we don't get updates)
189
- counters.keys.each do |k|
190
- @stats.counters[k] ||= 0 # Keep sending a 0 if we don't get updates
191
- end
192
-
193
200
  counters.each do |key, value|
194
- prefix = @opts[:prefix] ? "#{@opts[:prefix]}." : ""
195
- suffix = @opts[:suffix] ? ".#{@opts[:suffix]}" : ""
196
- updates << "#{prefix}#{key}#{suffix} #{value / @opts[:flush_interval]} #{now}"
201
+ updates << [metric_name(key),
202
+ value / @opts[:flush_interval],
203
+ now].join(" ")
197
204
  end # counters.each
198
205
 
199
206
  return updates.length == 0 ? nil : updates.join("\n") + "\n"
@@ -1,4 +1,3 @@
1
- require "em-zeromq"
2
1
  require "logger"
3
2
  require "statsdserver/proto/v1"
4
3
 
@@ -10,6 +9,16 @@ class StatsdServer
10
9
 
11
10
  public
12
11
  def initialize
12
+ begin
13
+ require "em-zeromq"
14
+ rescue LoadError => e
15
+ raise unless e.message =~ /em-zeromq/
16
+ new_e = \
17
+ e.exception("Please install the em-zeromq gem for ZeroMQ input.")
18
+ new_e.set_backtrace(e.backtrace)
19
+ raise new_e
20
+ end
21
+
13
22
  @logger = Logger.new(STDOUT)
14
23
  end
15
24
 
@@ -0,0 +1,29 @@
1
+ require "logger"
2
+
3
+ class StatsdServer
4
+ module Math
5
+ def self.summarize(values, opts = {})
6
+ opts = {
7
+ :percentile => 90,
8
+ }.merge(opts)
9
+ res = {}
10
+
11
+ values.sort!
12
+ res[:min] = values[0]
13
+ res[:max] = values[-1]
14
+ res[:mean] = res[:min]
15
+ res[:max_at_threshold] = res[:min]
16
+ if values.length > 1
17
+ threshold_index = ((100 - opts[:percentile]) / 100.0) * values.length
18
+ threshold_count = values.length - threshold_index.round
19
+ valid_values = values.slice(0, threshold_count)
20
+ res[:max_at_threshold] = valid_values[-1]
21
+ sum = 0
22
+ valid_values.each { |v| sum += v }
23
+ res[:mean] = sum / valid_values.length
24
+ end
25
+
26
+ return res
27
+ end
28
+ end # class Math
29
+ end # class StatsdServer
@@ -0,0 +1,60 @@
1
+ require "logger"
2
+
3
+ class StatsdServer::Output
4
+ class Amqp
5
+ attr_accessor :logger
6
+
7
+ public
8
+ def initialize(opts = {})
9
+ begin
10
+ require "bunny"
11
+ rescue LoadError => e
12
+ raise unless e.message =~ /bunny/
13
+ new_e = e.exception("Please install the bunny gem for AMQP output.")
14
+ new_e.set_backtrace(e.backtrace)
15
+ raise new_e
16
+ end
17
+
18
+ if opts["exchange_type"].nil?
19
+ raise ArgumentError, "missing host in [output:tcp] config section"
20
+ end
21
+
22
+ if opts["exchange_name"].nil?
23
+ raise ArgumentError, "missing port in [output:tcp] config section"
24
+ end
25
+
26
+ @opts = opts
27
+ @logger = Logger.new(STDOUT)
28
+ end
29
+
30
+ public
31
+ def send(str)
32
+ if @bunny.nil?
33
+ @bunny, @exchange = connect
34
+ end
35
+
36
+ begin
37
+ @exchange.publish(str)
38
+ rescue => e
39
+ @bunny.close_connection rescue nil
40
+ @bunny = nil
41
+ raise
42
+ end
43
+ end
44
+
45
+ private
46
+ def connect
47
+ bunny = Bunny.new(@opts)
48
+ bunny.start
49
+
50
+ exchange = bunny.exchange(
51
+ @opts["exchange_name"],
52
+ :type => @opts["exchange_type"].to_sym,
53
+ :durable => @opts["exchange_durable"] == "true" ? true : false,
54
+ :auto_delete => @opts["exchange_auto_delete"] == "true" ? true : false,
55
+ )
56
+
57
+ return bunny, exchange
58
+ end
59
+ end # class Amqp
60
+ end # class StatsdServer::Output
@@ -22,7 +22,14 @@ class StatsdServer::Output
22
22
  public
23
23
  def send(str)
24
24
  @socket ||= connect
25
- @socket.write("#{str}\n")
25
+ begin
26
+ @socket.write("#{str}")
27
+ rescue => e
28
+ # set @socket to nil to force a re-connect, then pass up the exception
29
+ @socket.close rescue nil
30
+ @socket = nil
31
+ raise
32
+ end
26
33
  end
27
34
 
28
35
  private
@@ -22,7 +22,7 @@ class StatsdServer
22
22
  raise ParseError, "invalid update: #{bit}"
23
23
  end
24
24
 
25
- if fields[1] == "ms" # timer update
25
+ if fields[1] == "ms" or fields[1] == "t" # timer update
26
26
  if fields[0].index(",")
27
27
  fields[0].split(",").each do |value_str|
28
28
  value = Integer(value_str) rescue nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statsdserver
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: '0.5'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-08 00:00:00.000000000 Z
12
+ date: 2012-08-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -44,7 +44,7 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
46
  - !ruby/object:Gem::Dependency
47
- name: amqp
47
+ name: daemons
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
@@ -107,6 +107,22 @@ dependencies:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: bunny
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
110
126
  - !ruby/object:Gem::Dependency
111
127
  name: em-zeromq
112
128
  requirement: !ruby/object:Gem::Requirement
@@ -115,7 +131,7 @@ dependencies:
115
131
  - - ! '>='
116
132
  - !ruby/object:Gem::Version
117
133
  version: '0'
118
- type: :runtime
134
+ type: :development
119
135
  prerelease: false
120
136
  version_requirements: !ruby/object:Gem::Requirement
121
137
  none: false
@@ -130,13 +146,15 @@ executables:
130
146
  extensions: []
131
147
  extra_rdoc_files: []
132
148
  files:
133
- - lib/statsdserver/output/tcp.rb
134
- - lib/statsdserver/output/stdout.rb
135
- - lib/statsdserver/input/zeromq.rb
149
+ - lib/statsdserver/math.rb
136
150
  - lib/statsdserver/input/udp.rb
137
- - lib/statsdserver/proto/v1.rb
138
- - lib/statsdserver/proto/parseerror.rb
151
+ - lib/statsdserver/input/zeromq.rb
152
+ - lib/statsdserver/output/stdout.rb
153
+ - lib/statsdserver/output/tcp.rb
154
+ - lib/statsdserver/output/amqp.rb
139
155
  - lib/statsdserver/stats.rb
156
+ - lib/statsdserver/proto/parseerror.rb
157
+ - lib/statsdserver/proto/v1.rb
140
158
  - lib/statsdserver.rb
141
159
  - bin/statsd
142
160
  homepage: https://github.com/fetep/ruby-statsd