multi-statsd 0.0.1
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/.gitignore +18 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +9 -0
- data/bin/multi-statsd +57 -0
- data/etc/multi-statsd.yml +17 -0
- data/lib/multi-statsd.rb +69 -0
- data/lib/multi-statsd/backends/base.rb +105 -0
- data/lib/multi-statsd/backends/hottie.rb +62 -0
- data/lib/multi-statsd/backends/stdout.rb +8 -0
- data/lib/multi-statsd/server.rb +22 -0
- data/lib/multi-statsd/version.rb +4 -0
- data/multi-statsd.gemspec +31 -0
- data/spec/backends/base_spec.rb +251 -0
- data/spec/backends/hottie_spec.rb +126 -0
- data/spec/backends/stdout_spec.rb +20 -0
- data/spec/multi-statsd_spec.rb +22 -0
- data/spec/server_spec.rb +41 -0
- data/spec/spec_helper.rb +6 -0
- metadata +205 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Kelley Reynolds
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# MultiStatsd [](https://travis-ci.org/bigcartel/multi-statsd) [](https://codeclimate.com/github/bigcartel/multi-statsd)
|
2
|
+
|
3
|
+
An eventmachine-based statsd server written in ruby with a modular backend system.
|
4
|
+
The motivation behind creating Yet Another Statsd Server was that while the existing ones allowed
|
5
|
+
you to specify different backends to flush data to, it was always aggregated the same way and flushed on the same interval.
|
6
|
+
This library incurs the overhead of multiple copies of your statistics with the tradeoff that you
|
7
|
+
can have backend-specific flush intervals and alternate aggregations of your data.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
$ gem install multi-statsd
|
12
|
+
|
13
|
+
## Configuration
|
14
|
+
|
15
|
+
Configuration is done via a YAML file which is specified on the command line. Example config file:
|
16
|
+
|
17
|
+
# Host and port to listen on
|
18
|
+
host: 127.0.0.1
|
19
|
+
port: 8125
|
20
|
+
verbosity: 1 # 0-4, 0 being the most verbose
|
21
|
+
logfile: /var/log/multi-statsd.log
|
22
|
+
pidfile: /var/run/multi-statsd.pid
|
23
|
+
daemonize: true
|
24
|
+
backends:
|
25
|
+
stdout_every_5:
|
26
|
+
backend: Stdout
|
27
|
+
flush_interval: 5
|
28
|
+
hottie:
|
29
|
+
backend: Hottie
|
30
|
+
host: 127.0.0.1
|
31
|
+
port: 6379
|
32
|
+
flush_interval: 1
|
33
|
+
seconds_to_retain: 60
|
34
|
+
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
Usage is straight forward:
|
39
|
+
|
40
|
+
$ multi-statsd -c /path/to/config
|
41
|
+
|
42
|
+
## Todo
|
43
|
+
|
44
|
+
1. Create graphite backend
|
45
|
+
2. Create relay backend
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
1. Fork it
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
51
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
52
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
53
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/multi-statsd
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
+
require 'yaml'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
|
8
|
+
parser = OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: multi-statsd -c CONFIG_FILE"
|
10
|
+
|
11
|
+
opts.separator ""
|
12
|
+
opts.separator "Options:"
|
13
|
+
|
14
|
+
opts.on("-c CONFIG_FILE", "--config-file CONFIG_FILE", "Configuration file") do |config_file|
|
15
|
+
options = YAML.load_file(config_file).merge(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on("-d", "--debug", "Shortcut for (daemonize: false, verbosity: 0, log: stdout)") do
|
19
|
+
options['daemonize'] = false
|
20
|
+
options['verbosity'] = 0
|
21
|
+
options['logfile'] = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
25
|
+
puts opts
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
parser.parse!
|
32
|
+
|
33
|
+
raise OptionParser::MissingArgument, "A config file must be specified" if options.empty?
|
34
|
+
raise "At least one backend must be specified" if !options.has_key?('backends') or options['backends'].empty?
|
35
|
+
|
36
|
+
require 'multi-statsd'
|
37
|
+
|
38
|
+
Signal.trap("INT") { MultiStatsd.stop }
|
39
|
+
Signal.trap("TERM") { MultiStatsd.stop }
|
40
|
+
|
41
|
+
log = options['logfile'] ? Logger.new(options['logfile']) : Logger.new(STDOUT)
|
42
|
+
log.level = options['verbosity'] || 2
|
43
|
+
MultiStatsd.logger = log
|
44
|
+
|
45
|
+
# Daemonize if requested
|
46
|
+
Process.daemon if options['daemonize']
|
47
|
+
|
48
|
+
# Write pidfile if requested
|
49
|
+
if options['pidfile']
|
50
|
+
begin
|
51
|
+
File.open(options['pidfile'], File::WRONLY | File::APPEND | File::CREAT) { |fp| fp.write Process.pid }
|
52
|
+
rescue
|
53
|
+
log.error "Unable to write pid file: #{$!.to_s}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
MultiStatsd.start(options).join
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Host and port to listen on
|
2
|
+
host: 127.0.0.1
|
3
|
+
port: 8125
|
4
|
+
verbosity: 1 # 0-4, 0 being the most verbose
|
5
|
+
# logfile: /var/log/multi-statsd.log
|
6
|
+
# pidfile: /var/run/multi-statsd.pid
|
7
|
+
# daemonize: false
|
8
|
+
backends:
|
9
|
+
stdout_every_3:
|
10
|
+
backend: Stdout
|
11
|
+
flush_interval: 3
|
12
|
+
# hottie:
|
13
|
+
# backend: Hottie
|
14
|
+
# host: 127.0.0.1
|
15
|
+
# port: 6379
|
16
|
+
# flush_interval: 1
|
17
|
+
# seconds_to_retain: 60
|
data/lib/multi-statsd.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'em-logger'
|
4
|
+
|
5
|
+
require "multi-statsd/version"
|
6
|
+
require 'multi-statsd/backends/base'
|
7
|
+
require 'multi-statsd/server'
|
8
|
+
|
9
|
+
# MultiStatsd namespace
|
10
|
+
module MultiStatsd
|
11
|
+
# Assign a logger
|
12
|
+
# @param [Logger] logger
|
13
|
+
def self.logger=(logger)
|
14
|
+
@logger = logger.kind_of?(EM::Logger) ? logger : EM::Logger.new(logger)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return the logger
|
18
|
+
# @return [Logger]
|
19
|
+
def self.logger
|
20
|
+
return @logger if defined?(@logger)
|
21
|
+
log = Logger.new(STDOUT)
|
22
|
+
log.level = 2
|
23
|
+
@logger = EM::Logger.new(log)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Start up the Eventmachine reactor loop in a separate thread give a set of options.
|
27
|
+
# This is a test
|
28
|
+
# This is another test
|
29
|
+
# @param [Hash] options Set of options
|
30
|
+
# @return [thread] Thread which is running the eventmachine loop
|
31
|
+
def self.start(options)
|
32
|
+
thread = Thread.new do
|
33
|
+
EM.run do
|
34
|
+
backends = []
|
35
|
+
options['backends'].each_pair do |name, options|
|
36
|
+
backend = options.delete('backend')
|
37
|
+
begin
|
38
|
+
require "multi-statsd/backends/#{backend.downcase}"
|
39
|
+
rescue LoadError
|
40
|
+
raise MultiStatsd::Backend::Error, "Cannot load file multi-statsd/backends/#{backend.downcase}"
|
41
|
+
end
|
42
|
+
if !MultiStatsd::Backend.const_defined?(backend)
|
43
|
+
raise MultiStatsd::Backend::Error, "No such back end: MultiStatsd::Backend::#{backend}"
|
44
|
+
else
|
45
|
+
logger.info "Adding backend #{backend} :: #{name}"
|
46
|
+
backends << MultiStatsd::Backend.const_get(backend).new(name, options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
EM::open_datagram_socket(
|
51
|
+
(options['host'] || '127.0.0.1'),
|
52
|
+
(options['port'] || 8125),
|
53
|
+
MultiStatsd::Server,
|
54
|
+
backends
|
55
|
+
)
|
56
|
+
logger.info "multi-statsd starting up on #{options['host']}:#{options['port']}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
thread.abort_on_exception = true
|
61
|
+
thread
|
62
|
+
end
|
63
|
+
|
64
|
+
# Stop the Eventmachine reactor loop
|
65
|
+
def self.stop
|
66
|
+
logger.info "multi-statsd shutting down"
|
67
|
+
EM.next_tick { EM.stop }
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module MultiStatsd
|
5
|
+
# Various backends should be defined in this module
|
6
|
+
module Backend
|
7
|
+
# Error class for MultiStatsd-generated errors
|
8
|
+
class Error < StandardError; end
|
9
|
+
|
10
|
+
# @abstract Subclass and override {#flush} to implement a custom Backend class.
|
11
|
+
class Base
|
12
|
+
attr_reader :counters, :timers, :gauges, :name
|
13
|
+
|
14
|
+
def initialize(name, options = {})
|
15
|
+
@name, @options = name, options
|
16
|
+
@timers, @gauges, @counters = {}, {}, Hash.new(0)
|
17
|
+
@semaphore = Mutex.new
|
18
|
+
|
19
|
+
@options['flush_interval'] ||= 15
|
20
|
+
|
21
|
+
post_init
|
22
|
+
|
23
|
+
EventMachine::add_periodic_timer(@options['flush_interval']) do
|
24
|
+
EM.defer { flush }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Integer] the flush interval
|
29
|
+
def flush_interval
|
30
|
+
@options['flush_interval']
|
31
|
+
end
|
32
|
+
|
33
|
+
# Override in subclasses to execute code after initialization (eg. database connection setup)
|
34
|
+
def post_init
|
35
|
+
end
|
36
|
+
|
37
|
+
# Each backend must implement this method to flush its data
|
38
|
+
def flush
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
# Reset and return the generated data in a mutex to ensure none are lost
|
43
|
+
# @return [Array] An array consisting of [counters, timers, gauges]
|
44
|
+
def reset_stats
|
45
|
+
@semaphore.synchronize do
|
46
|
+
counters = @counters.dup
|
47
|
+
timers = @timers.dup
|
48
|
+
gauges = @gauges.dup
|
49
|
+
@counters.clear
|
50
|
+
@timers.clear
|
51
|
+
[counters, timers, gauges]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Record data in statsd format
|
56
|
+
# Gauges - cpu:0.15|g
|
57
|
+
# Timers - api:12|ms
|
58
|
+
# Counters - bytes:123|c
|
59
|
+
# Counters with sampling - bytes:123|c|@0.1
|
60
|
+
# Multiple values - api:12|ms:15|ms:8|ms
|
61
|
+
# @param [String] msg string of data in statsd format
|
62
|
+
# @return [true]
|
63
|
+
def write(msg)
|
64
|
+
msg.each_line do |row|
|
65
|
+
# Fetch our key and records
|
66
|
+
key, *records = row.split(":")
|
67
|
+
|
68
|
+
# Clean up the key formatting
|
69
|
+
key = format_key(key)
|
70
|
+
|
71
|
+
# Iterate through each record and store the data
|
72
|
+
records.each do |record|
|
73
|
+
value, type, sample_rate = record.split('|')
|
74
|
+
next unless value and type and value =~ /^(?:[\d\.-]+)$/
|
75
|
+
|
76
|
+
case type
|
77
|
+
when "ms"
|
78
|
+
@timers[key] ||= []
|
79
|
+
@timers[key].push(value.to_f)
|
80
|
+
when "c"
|
81
|
+
if sample_rate
|
82
|
+
sample_rate = sample_rate.gsub(/[^\d\.]/, '').to_f
|
83
|
+
sample_rate = 1 if sample_rate <= 0
|
84
|
+
@counters[key] += value.to_f * (1.0 / sample_rate)
|
85
|
+
else
|
86
|
+
@counters[key] += value.to_f
|
87
|
+
end
|
88
|
+
when "g"
|
89
|
+
@gauges[key] = value.to_f
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
# Format a given key
|
98
|
+
# @param [String] key from the statsd record
|
99
|
+
# @return [String] formatted key
|
100
|
+
def format_key(key)
|
101
|
+
key.gsub(/\s+/, '_').gsub(/\//, '-').gsub(/[^a-zA-Z_\-0-9\.]/, '')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
# Hottie backend
|
4
|
+
# Hottie is a redis-based short-term backend to enable real-time visibility into application behavior.
|
5
|
+
# It is specifically designed to enable real-time heatmap/histogram visualizations from timer data.
|
6
|
+
class MultiStatsd::Backend::Hottie < MultiStatsd::Backend::Base
|
7
|
+
attr_reader :seconds_to_retain, :samples_to_retain
|
8
|
+
|
9
|
+
# Initialize a connection to redis and configure our samples to retain
|
10
|
+
def post_init
|
11
|
+
@db = Redis.new(
|
12
|
+
:host => (@options['host'] || '127.0.0.1'),
|
13
|
+
:port => (@options['port'] || 6379),
|
14
|
+
:database => (@options['database'] || 1)
|
15
|
+
)
|
16
|
+
@seconds_to_retain = @options['seconds_to_retain'] || 60
|
17
|
+
@samples_to_retain = (@seconds_to_retain / @options['flush_interval']).floor
|
18
|
+
end
|
19
|
+
|
20
|
+
# Flush the data to redis in the format required for Hottie
|
21
|
+
# @return [Float] The number of seconds it took to aggregate/flush the data to redis
|
22
|
+
def flush
|
23
|
+
counters, timers, gauges = reset_stats
|
24
|
+
ts = Time.new.to_i
|
25
|
+
time = ::Benchmark.realtime do
|
26
|
+
@db.pipelined do
|
27
|
+
if !gauges.empty?
|
28
|
+
@db.hmset "gauges:#{ts}", *(gauges.map { |stat, gauge| [stat, gauge] }.flatten)
|
29
|
+
@db.expire "gauges:#{ts}", @seconds_to_retain + 5 # Retain a few extra seconds to avoid reporting race
|
30
|
+
@db.sadd "gauges", gauges.keys
|
31
|
+
end
|
32
|
+
@db.lpush "gauge_samples", "gauges:#{ts}"
|
33
|
+
@db.ltrim "gauge_samples", 0, @samples_to_retain - 1
|
34
|
+
|
35
|
+
if !counters.empty?
|
36
|
+
@db.hmset "counters:#{ts}", *(counters.map { |stat, counter|[stat, counter / @options['flush_interval']] }.flatten)
|
37
|
+
@db.expire "counters:#{ts}", @seconds_to_retain + 5 # Retain a few extra seconds to avoid reporting race
|
38
|
+
@db.sadd "counters", counters.keys
|
39
|
+
end
|
40
|
+
@db.lpush "counter_samples", "counters:#{ts}"
|
41
|
+
@db.ltrim "counter_samples", 0, @samples_to_retain - 1
|
42
|
+
|
43
|
+
if !timers.empty?
|
44
|
+
timer_hash = Hash.new(0)
|
45
|
+
@db.hmset "timers:#{ts}", *(timers.map { |stat, data|
|
46
|
+
timer_hash.clear
|
47
|
+
data.each { |d| timer_hash[d.round] += 1 }
|
48
|
+
[stat, Marshal.dump(timer_hash)]
|
49
|
+
})
|
50
|
+
@db.expire "timers:#{ts}", @seconds_to_retain + 5 # Retain a few extra seconds to avoid reporting race
|
51
|
+
@db.sadd "timers", timers.keys
|
52
|
+
end
|
53
|
+
@db.lpush "timer_samples", "timers:#{ts}"
|
54
|
+
@db.ltrim "timer_samples", 0, @samples_to_retain - 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
MultiStatsd.logger.debug "Hottie flushing took #{"%.3f" % (time * 1000)}ms"
|
58
|
+
time
|
59
|
+
rescue Redis::CannotConnectError
|
60
|
+
MultiStatsd.logger.warning "Unable to connect to redis, skipping flush"
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Example backend which prints out the all stats to stdout
|
2
|
+
class MultiStatsd::Backend::Stdout < MultiStatsd::Backend::Base
|
3
|
+
# Prints the name of this backend, the current time, and inspected stats to stdout
|
4
|
+
# @return [nil]
|
5
|
+
def flush
|
6
|
+
$stdout.puts "[#{@name}:#{Time.now.to_i}] #{reset_stats.inspect}"
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MultiStatsd
|
2
|
+
# Eventmachine connection which receives UDP data and writes it to various backends
|
3
|
+
class Server < EventMachine::Connection
|
4
|
+
# Initialize the server with one or more backends
|
5
|
+
# @param [MultiStatsd::Backend] backends One or more backends that will receive data
|
6
|
+
def initialize(backends = [], *args)
|
7
|
+
@backends = [backends].flatten
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
# Write out statsd data to each registered backend
|
12
|
+
# @param [String] data Data in statsd format
|
13
|
+
# @return [nil]
|
14
|
+
def receive_data(data)
|
15
|
+
@backends.each do |backend|
|
16
|
+
backend.write(data)
|
17
|
+
end
|
18
|
+
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'multi-statsd/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "multi-statsd"
|
8
|
+
gem.version = MultiStatsd::VERSION
|
9
|
+
gem.authors = ["Kelley Reynolds"]
|
10
|
+
gem.email = ["kelley@bigcartel.com"]
|
11
|
+
gem.description = %q{Statsd Server with flexible aggregation and back-end support}
|
12
|
+
gem.summary = %q{Statsd Server with flexible aggregation and back-end support}
|
13
|
+
gem.homepage = "https://github.com/bigcartel/multi-statsd"
|
14
|
+
gem.rubyforge_project = "multi-statsd"
|
15
|
+
|
16
|
+
gem.add_dependency "eventmachine"
|
17
|
+
gem.add_dependency "em-logger"
|
18
|
+
|
19
|
+
gem.required_ruby_version = '>= 1.9.3'
|
20
|
+
gem.add_development_dependency "bundler", ">= 1.0.0"
|
21
|
+
gem.add_development_dependency "simplecov"
|
22
|
+
gem.add_development_dependency "rspec", ">= 2.6.0"
|
23
|
+
gem.add_development_dependency "yard", ">= 0.8"
|
24
|
+
gem.add_development_dependency "rake"
|
25
|
+
gem.add_development_dependency "redis"
|
26
|
+
|
27
|
+
gem.files = `git ls-files`.split($/)
|
28
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
29
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
30
|
+
gem.require_paths = ["lib"]
|
31
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
3
|
+
|
4
|
+
module EventMachine
|
5
|
+
def self.add_periodic_timer(interval)
|
6
|
+
# Don't flush anything automatically
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe MultiStatsd::Backend::Base do
|
11
|
+
let(:backend) { MultiStatsd::Backend::Base.new('base') }
|
12
|
+
|
13
|
+
describe "default initialization" do
|
14
|
+
it "should have a name" do
|
15
|
+
backend.name.should == 'base'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have a default flush_interval" do
|
19
|
+
backend.instance_variable_get(:@options)['flush_interval'].should == 15
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should have a hash of counters, timers, and gauges" do
|
23
|
+
backend.counters.should be_kind_of(Hash)
|
24
|
+
backend.timers.should be_kind_of(Hash)
|
25
|
+
backend.gauges.should be_kind_of(Hash)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "custom initialization" do
|
30
|
+
let(:backend) { MultiStatsd::Backend::Base.new('base', 'flush_interval' => 20) }
|
31
|
+
|
32
|
+
it "should have a default flush_interval" do
|
33
|
+
backend.instance_variable_get(:@options)['flush_interval'].should == 20
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "flush" do
|
38
|
+
it "should raise an error" do
|
39
|
+
lambda { backend.flush }.should raise_error NotImplementedError
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "reset_stats" do
|
44
|
+
let(:timers) { {'api' => [22, 15] } }
|
45
|
+
|
46
|
+
it "should return an array of hashes" do
|
47
|
+
stats = backend.reset_stats
|
48
|
+
stats.should be_kind_of Array
|
49
|
+
stats.each { |stat|
|
50
|
+
stat.should be_kind_of Hash
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "counters" do
|
55
|
+
let(:counters) { {'bytes' => 15 } }
|
56
|
+
|
57
|
+
before(:each) {
|
58
|
+
backend.instance_variable_set(:@counters, counters.dup)
|
59
|
+
@stats = backend.reset_stats[0]
|
60
|
+
}
|
61
|
+
|
62
|
+
it "should return the data" do
|
63
|
+
@stats.should == counters
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should be a duplicated object" do
|
67
|
+
@stats.object_id.should_not == backend.counters.object_id
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should clear the instance variable" do
|
71
|
+
backend.counters.should be_empty
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "timers" do
|
76
|
+
let(:timers) { {'api' => [9, 5] } }
|
77
|
+
|
78
|
+
before(:each) {
|
79
|
+
backend.instance_variable_set(:@timers, timers.dup)
|
80
|
+
@stats = backend.reset_stats[1]
|
81
|
+
}
|
82
|
+
|
83
|
+
it "should return the data" do
|
84
|
+
@stats.should == timers
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should be a duplicated object" do
|
88
|
+
@stats.object_id.should_not == backend.timers.object_id
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should clear the instance variable" do
|
92
|
+
backend.timers.should be_empty
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "gauges" do
|
97
|
+
let(:gauges) { {'cpu' => 0.15 } }
|
98
|
+
|
99
|
+
before(:each) {
|
100
|
+
backend.instance_variable_set(:@gauges, gauges.dup)
|
101
|
+
@stats = backend.reset_stats[2]
|
102
|
+
}
|
103
|
+
|
104
|
+
it "should return the data" do
|
105
|
+
@stats.should == gauges
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should be a duplicated object" do
|
109
|
+
@stats.object_id.should_not == backend.gauges.object_id
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should not clear the instance variable" do
|
113
|
+
backend.gauges.should_not be_empty
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "write" do
|
119
|
+
describe "crap" do
|
120
|
+
it "should ignore garbage" do
|
121
|
+
backend.write "blah:123|foo"
|
122
|
+
backend.write "blah:snord|ms"
|
123
|
+
backend.counters.should be_empty
|
124
|
+
backend.gauges.should be_empty
|
125
|
+
backend.timers.should be_empty
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "timers" do
|
130
|
+
describe "single" do
|
131
|
+
let(:record) { "api:3|ms" }
|
132
|
+
before(:each) { backend.write record }
|
133
|
+
|
134
|
+
it "should record from scratch" do
|
135
|
+
backend.timers.should == {'api' => [3]}
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should append a time" do
|
139
|
+
backend.write "api:4|ms"
|
140
|
+
backend.timers.should == {'api' => [3, 4]}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "multiple" do
|
145
|
+
let(:record) { "api:3|ms:4|ms" }
|
146
|
+
before(:each) { backend.write record }
|
147
|
+
|
148
|
+
it "should record both values" do
|
149
|
+
backend.timers.should == {'api' => [3,4]}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "gauges" do
|
155
|
+
describe "single" do
|
156
|
+
let(:record) { "cpu:0.15|g" }
|
157
|
+
before(:each) { backend.write record }
|
158
|
+
|
159
|
+
it "should record from scratch" do
|
160
|
+
backend.gauges.should == {'cpu' => 0.15}
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should update existing gauge" do
|
164
|
+
backend.write "cpu:0.17|g"
|
165
|
+
backend.gauges.should == {'cpu' => 0.17}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "multiple" do
|
170
|
+
let(:record) { "cpu:0.15|g:0.17|g" }
|
171
|
+
before(:each) { backend.write record }
|
172
|
+
|
173
|
+
it "should use the last value" do
|
174
|
+
backend.gauges.should == {'cpu' => 0.17}
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "counters" do
|
180
|
+
describe "single without sample rate" do
|
181
|
+
let(:record) { "bytes:15.2|c" }
|
182
|
+
before(:each) { backend.write record }
|
183
|
+
|
184
|
+
it "should record from scratch" do
|
185
|
+
backend.counters.should == {'bytes' => 15.2}
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should add existing record" do
|
189
|
+
backend.write record
|
190
|
+
backend.counters.should == {'bytes' => 30.4}
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "single with sample rate" do
|
195
|
+
let(:record) { "bytes:15.2|c|@0.5" }
|
196
|
+
before(:each) { backend.write record }
|
197
|
+
|
198
|
+
it "should record from scratch" do
|
199
|
+
backend.counters.should == {'bytes' => 30.4}
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should add existing record" do
|
203
|
+
backend.write record
|
204
|
+
backend.counters.should == {'bytes' => 60.8}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "multiple without sample rate" do
|
209
|
+
let(:record) { "bytes:15.5|c:20.2|c" }
|
210
|
+
before(:each) { backend.write record }
|
211
|
+
|
212
|
+
it "should record from scratch" do
|
213
|
+
backend.counters.should == {'bytes' => 35.7}
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should add existing record" do
|
217
|
+
backend.write record
|
218
|
+
backend.counters.should == {'bytes' => 71.4}
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "multiple with sample rate" do
|
223
|
+
let(:record) { "bytes:7.75|c|@0.5:10.1|c|@0.5" }
|
224
|
+
before(:each) { backend.write record }
|
225
|
+
|
226
|
+
it "should record from scratch" do
|
227
|
+
backend.counters.should == {'bytes' => 35.7}
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should add existing record" do
|
231
|
+
backend.write record
|
232
|
+
backend.counters.should == {'bytes' => 71.4}
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe "format_key" do
|
239
|
+
it "should turn spaces into underscores" do
|
240
|
+
backend.format_key("blah foo").should == "blah_foo"
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should turn slashes into hyphens" do
|
244
|
+
backend.format_key("blah/foo").should == "blah-foo"
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should filter out non-alpha and a few selected special chars" do
|
248
|
+
backend.format_key("blahüfoo*!@&#^%}").should == "blahfoo"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
3
|
+
require 'multi-statsd/backends/hottie'
|
4
|
+
|
5
|
+
module EventMachine
|
6
|
+
def self.add_periodic_timer(interval)
|
7
|
+
# Don't flush anything automatically
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe MultiStatsd::Backend::Hottie do
|
12
|
+
let(:backend) { MultiStatsd::Backend::Hottie.new('hottie') }
|
13
|
+
|
14
|
+
describe "post init" do
|
15
|
+
it "should have a connection to redis" do
|
16
|
+
backend.instance_variable_get(:@db).should be_kind_of Redis
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should have a default @seconds_to_retain" do
|
20
|
+
backend.seconds_to_retain.should == 60
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should have a default @seconds_to_retain" do
|
24
|
+
backend = MultiStatsd::Backend::Hottie.new('hottie', 'seconds_to_retain' => 80)
|
25
|
+
backend.seconds_to_retain.should == 80
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should have an appropriate set of samples to retain" do
|
29
|
+
backend.samples_to_retain.should == 4
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should have a default @seconds_to_retain" do
|
33
|
+
backend = MultiStatsd::Backend::Hottie.new('hottie', 'seconds_to_retain' => 80, 'flush_interval' => 10)
|
34
|
+
backend.samples_to_retain.should == 8
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "flush" do
|
39
|
+
describe "gauges" do
|
40
|
+
before(:all) {
|
41
|
+
@db = backend.instance_variable_get(:@db)
|
42
|
+
@db.flushdb
|
43
|
+
backend.write "cpu:0.15|g"
|
44
|
+
backend.flush
|
45
|
+
}
|
46
|
+
|
47
|
+
it "should add the name to the set" do
|
48
|
+
@db.sismember("gauges", "cpu").should be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should have a sample" do
|
52
|
+
samples = @db.lrange "gauge_samples", 0, -1
|
53
|
+
samples.size.should == 1
|
54
|
+
@db.hlen(samples.first).should == 1
|
55
|
+
@db.hget(samples.first, 'cpu').should == "0.15"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should roll over on flush if there are too many samples" do
|
59
|
+
backend.samples_to_retain.times do |i|
|
60
|
+
@db.lpush "gauge_samples", "gauge:#{i}"
|
61
|
+
end
|
62
|
+
@db.llen("gauge_samples").should == (backend.samples_to_retain + 1)
|
63
|
+
backend.flush
|
64
|
+
@db.llen("gauge_samples").should == backend.samples_to_retain
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "counters" do
|
69
|
+
before(:all) {
|
70
|
+
@db = backend.instance_variable_get(:@db)
|
71
|
+
@db.flushdb
|
72
|
+
backend.write "bytes:1200|c"
|
73
|
+
backend.flush
|
74
|
+
}
|
75
|
+
|
76
|
+
it "should add the name to the set" do
|
77
|
+
@db.sismember("counters", "bytes").should be_true
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should have a sample" do
|
81
|
+
samples = @db.lrange "counter_samples", 0, -1
|
82
|
+
samples.size.should == 1
|
83
|
+
@db.hlen(samples.first).should == 1
|
84
|
+
(@db.hget(samples.first, 'bytes')).to_i.should == (1200 / backend.flush_interval)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should roll over on flush if there are too many samples" do
|
88
|
+
backend.samples_to_retain.times do |i|
|
89
|
+
@db.lpush "counter_samples", "counter:#{i}"
|
90
|
+
end
|
91
|
+
@db.llen("counter_samples").should == (backend.samples_to_retain + 1)
|
92
|
+
backend.flush
|
93
|
+
@db.llen("counter_samples").should == backend.samples_to_retain
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "timers" do
|
98
|
+
before(:all) {
|
99
|
+
@db = backend.instance_variable_get(:@db)
|
100
|
+
@db.flushdb
|
101
|
+
backend.write "api:15.5|ms"
|
102
|
+
backend.flush
|
103
|
+
}
|
104
|
+
|
105
|
+
it "should add the name to the set" do
|
106
|
+
@db.sismember("timers", "api").should be_true
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should have a rounded sample" do
|
110
|
+
samples = @db.lrange "timer_samples", 0, -1
|
111
|
+
samples.size.should == 1
|
112
|
+
@db.hlen(samples.first).should == 1
|
113
|
+
Marshal.load(@db.hget(samples.first, 'api')).should == {16 => 1}
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should roll over on flush if there are too many samples" do
|
117
|
+
backend.samples_to_retain.times do |i|
|
118
|
+
@db.lpush "timer_samples", "timer:#{i}"
|
119
|
+
end
|
120
|
+
@db.llen("timer_samples").should == (backend.samples_to_retain + 1)
|
121
|
+
backend.flush
|
122
|
+
@db.llen("timer_samples").should == backend.samples_to_retain
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
3
|
+
require 'multi-statsd/backends/stdout'
|
4
|
+
|
5
|
+
module EventMachine
|
6
|
+
def self.add_periodic_timer(interval)
|
7
|
+
# Don't flush anything automatically
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe MultiStatsd::Backend::Stdout do
|
12
|
+
let(:backend) { MultiStatsd::Backend::Stdout.new('stdout') }
|
13
|
+
|
14
|
+
describe "flush behavior" do
|
15
|
+
it "should print to stdout" do
|
16
|
+
$stdout.should_receive(:puts)
|
17
|
+
backend.flush
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path('spec_helper.rb', File.dirname(__FILE__))
|
3
|
+
require 'multi-statsd/backends/stdout'
|
4
|
+
|
5
|
+
describe MultiStatsd do
|
6
|
+
it "should start and stop the reactor" do
|
7
|
+
MultiStatsd.start({'host' => 'localhost', 'port' => 33333, 'backends' => {'stdout' => {'backend' => 'Stdout'}}})
|
8
|
+
sleep 0.2
|
9
|
+
EM.reactor_running?.should be_true
|
10
|
+
MultiStatsd.stop
|
11
|
+
sleep 0.2
|
12
|
+
EM.reactor_running?.should be_false
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should raise an error on an unknown backend" do
|
16
|
+
lambda {
|
17
|
+
MultiStatsd.start({'host' => 'localhost', 'port' => 33333, 'backends' => {'stdout' => {'backend' => 'Broken'}}})
|
18
|
+
sleep 1
|
19
|
+
|
20
|
+
}.should raise_error(MultiStatsd::Backend::Error)
|
21
|
+
end
|
22
|
+
end
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path('spec_helper.rb', File.dirname(__FILE__))
|
3
|
+
require 'multi-statsd/backends/stdout'
|
4
|
+
|
5
|
+
class MockBackend
|
6
|
+
def write(data); end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe MultiStatsd::Server do
|
10
|
+
def send_message(message, server='localhost', port=33333)
|
11
|
+
UDPSocket.new.send(message, 0, server, port)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:message) { 'test:1|c' }
|
15
|
+
|
16
|
+
describe "recording data to backends" do
|
17
|
+
it "Should record data to a single backend" do
|
18
|
+
mock_backend = MockBackend.new
|
19
|
+
mock_backend.should_receive(:write).with(message)
|
20
|
+
EM.run do
|
21
|
+
EM::open_datagram_socket 'localhost', 33333, MultiStatsd::Server, mock_backend
|
22
|
+
send_message message
|
23
|
+
EM.next_tick { EM.stop }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should record data to multiple backends" do
|
28
|
+
backends = 3.times.map do
|
29
|
+
backend = MockBackend.new
|
30
|
+
backend.should_receive(:write).with(message)
|
31
|
+
backend
|
32
|
+
end
|
33
|
+
|
34
|
+
EM.run do
|
35
|
+
EM::open_datagram_socket 'localhost', 33333, MultiStatsd::Server, backends
|
36
|
+
send_message message
|
37
|
+
EM.next_tick { EM.stop }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: multi-statsd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kelley Reynolds
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: em-logger
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.0.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.0.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.6.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 2.6.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: yard
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0.8'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.8'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rake
|
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'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: redis
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
description: Statsd Server with flexible aggregation and back-end support
|
143
|
+
email:
|
144
|
+
- kelley@bigcartel.com
|
145
|
+
executables:
|
146
|
+
- multi-statsd
|
147
|
+
extensions: []
|
148
|
+
extra_rdoc_files: []
|
149
|
+
files:
|
150
|
+
- .gitignore
|
151
|
+
- .travis.yml
|
152
|
+
- Gemfile
|
153
|
+
- LICENSE.txt
|
154
|
+
- README.md
|
155
|
+
- Rakefile
|
156
|
+
- bin/multi-statsd
|
157
|
+
- etc/multi-statsd.yml
|
158
|
+
- lib/multi-statsd.rb
|
159
|
+
- lib/multi-statsd/backends/base.rb
|
160
|
+
- lib/multi-statsd/backends/hottie.rb
|
161
|
+
- lib/multi-statsd/backends/stdout.rb
|
162
|
+
- lib/multi-statsd/server.rb
|
163
|
+
- lib/multi-statsd/version.rb
|
164
|
+
- multi-statsd.gemspec
|
165
|
+
- spec/backends/base_spec.rb
|
166
|
+
- spec/backends/hottie_spec.rb
|
167
|
+
- spec/backends/stdout_spec.rb
|
168
|
+
- spec/multi-statsd_spec.rb
|
169
|
+
- spec/server_spec.rb
|
170
|
+
- spec/spec_helper.rb
|
171
|
+
homepage: https://github.com/bigcartel/multi-statsd
|
172
|
+
licenses: []
|
173
|
+
post_install_message:
|
174
|
+
rdoc_options: []
|
175
|
+
require_paths:
|
176
|
+
- lib
|
177
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
178
|
+
none: false
|
179
|
+
requirements:
|
180
|
+
- - ! '>='
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: 1.9.3
|
183
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
|
+
none: false
|
185
|
+
requirements:
|
186
|
+
- - ! '>='
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
segments:
|
190
|
+
- 0
|
191
|
+
hash: 1318037612798865336
|
192
|
+
requirements: []
|
193
|
+
rubyforge_project: multi-statsd
|
194
|
+
rubygems_version: 1.8.23
|
195
|
+
signing_key:
|
196
|
+
specification_version: 3
|
197
|
+
summary: Statsd Server with flexible aggregation and back-end support
|
198
|
+
test_files:
|
199
|
+
- spec/backends/base_spec.rb
|
200
|
+
- spec/backends/hottie_spec.rb
|
201
|
+
- spec/backends/stdout_spec.rb
|
202
|
+
- spec/multi-statsd_spec.rb
|
203
|
+
- spec/server_spec.rb
|
204
|
+
- spec/spec_helper.rb
|
205
|
+
has_rdoc:
|