statsdserver 0.4 → 0.5

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/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