rstatsd 0.2
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 +17 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +49 -0
- data/Rakefile +2 -0
- data/bin/rstatsd +3 -0
- data/lib/rstatsd.rb +10 -0
- data/lib/rstatsd/autorun.rb +9 -0
- data/lib/rstatsd/chart.rb +51 -0
- data/lib/rstatsd/collector.rb +45 -0
- data/lib/rstatsd/config.rb +23 -0
- data/lib/rstatsd/helpers.rb +45 -0
- data/lib/rstatsd/server.rb +38 -0
- data/lib/rstatsd/version.rb +3 -0
- data/rstatsd.gemspec +27 -0
- data/spec/benchmark.rb +11 -0
- data/spec/rstatsd/chart_spec.rb +77 -0
- data/spec/rstatsd/collector_spec.rb +82 -0
- data/spec/rstatsd/config_spec.rb +19 -0
- data/spec/rstatsd/helpers_spec.rb +66 -0
- data/spec/rstatsd/server_spec.rb +4 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/connection_helper.rb +9 -0
- data/spec/support/redis_mock.rb +65 -0
- data/templates/bootstrap.css +3496 -0
- data/templates/google_chart.erb +41 -0
- data/templates/index.html +38 -0
- metadata +160 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-1.9.2-p290@rstatsd
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Dan Herrera
|
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,49 @@
|
|
1
|
+
# rstatsd
|
2
|
+
|
3
|
+
rstatsd is a ruby based daemon for capturing data from statsd clients.
|
4
|
+
|
5
|
+
rstatsd is inspired by the work at etsy to measure everything, measure
|
6
|
+
anything. They use a combination of node.js and graphite to capture and
|
7
|
+
graph this data.
|
8
|
+
|
9
|
+
The goal of this project was to replicate this light-weight approach and
|
10
|
+
reduce the number of dependencies to do this to two: redis and ruby.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
$ gem install rstatsd
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
Start redis
|
19
|
+
|
20
|
+
$ brew install redis
|
21
|
+
$ redis-server /usr/local/etc/redis.conf
|
22
|
+
|
23
|
+
|
24
|
+
Start the collection daemon and server
|
25
|
+
|
26
|
+
$ rstatsd
|
27
|
+
|
28
|
+
Add some data (you'll need a statsd compatible client like statsd-ruby)
|
29
|
+
|
30
|
+
$ irb
|
31
|
+
irb> require 'statsd'
|
32
|
+
=> true
|
33
|
+
irb> s = Statsd.new('localhost')
|
34
|
+
=> #<Statsd:0x007fee419866d8 @host="localhost", @port=8125>
|
35
|
+
irb(main):004:0> s.increment('grebulons')
|
36
|
+
=> 10
|
37
|
+
irb> s.increment('grebulons')
|
38
|
+
=> 10
|
39
|
+
irb> s.increment('grebulons')
|
40
|
+
=> 10
|
41
|
+
irb> s.increment('grebulons')
|
42
|
+
=> 10
|
43
|
+
|
44
|
+
|
45
|
+
Then view the result in a web browser
|
46
|
+
|
47
|
+
irb> `open http://localhost:8126/?target=grebulons`
|
48
|
+
|
49
|
+
Bask in the something of something-something.
|
data/Rakefile
ADDED
data/bin/rstatsd
ADDED
data/lib/rstatsd.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require_relative 'server'
|
2
|
+
require_relative 'collector'
|
3
|
+
|
4
|
+
EventMachine::run {
|
5
|
+
EventMachine::open_datagram_socket("127.0.0.1", 8125, Rstatsd::Collector)
|
6
|
+
EventMachine::start_server("127.0.0.1", 8126, Rstatsd::Server)
|
7
|
+
puts "http server running at http://localhost:8126/"
|
8
|
+
puts "go there for more instructions"
|
9
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Rstatsd
|
4
|
+
class Chart
|
5
|
+
include Rstatsd::Helpers
|
6
|
+
|
7
|
+
attr_accessor :data
|
8
|
+
|
9
|
+
def initialize(query_string)
|
10
|
+
@query_string = query_string
|
11
|
+
@data = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def targets
|
15
|
+
@query_string.split("&").map do |term|
|
16
|
+
param_name, value = term.split("=")
|
17
|
+
value if param_name == 'target'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def column_types
|
22
|
+
targets.inject([['datetime', 'Timestamp']]) do |memo, target|
|
23
|
+
memo << ['number', target.capitalize]
|
24
|
+
memo
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def title
|
29
|
+
@query_string.split("&").detect do |term|
|
30
|
+
param_name, value = term.split("=")
|
31
|
+
if param_name == 'title'
|
32
|
+
return URI.unescape(value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
targets.map(&:capitalize).join(', ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def draw_chart
|
39
|
+
@data = JSON.dump(fetch_counters(targets))
|
40
|
+
yield self
|
41
|
+
end
|
42
|
+
|
43
|
+
def width
|
44
|
+
800
|
45
|
+
end
|
46
|
+
|
47
|
+
def height
|
48
|
+
480
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'em-hiredis'
|
2
|
+
require_relative './helpers'
|
3
|
+
|
4
|
+
module Rstatsd
|
5
|
+
class Collector < EventMachine::Connection
|
6
|
+
include Rstatsd::Helpers
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
@redis = EM::Hiredis.connect
|
11
|
+
end
|
12
|
+
|
13
|
+
def post_init
|
14
|
+
end
|
15
|
+
|
16
|
+
def receive_data(data)
|
17
|
+
bits = data.split(':')
|
18
|
+
key = format_key(bits.first)
|
19
|
+
|
20
|
+
fields = bits.last.split("|")
|
21
|
+
case fields[1]
|
22
|
+
when 'c'
|
23
|
+
if fields[0] == '1'
|
24
|
+
@redis.incr(key).callback do |value|
|
25
|
+
@redis.rpush(counter_key_name(key), "#{value}:#{Time.now.to_i}")
|
26
|
+
end
|
27
|
+
elsif fields[0] == '-1'
|
28
|
+
@redis.decr(key).callback do |value|
|
29
|
+
@redis.rpush(counter_key_name(key), "#{value}:#{Time.now.to_i}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@redis.ltrim(counter_key_name(key), 10000)
|
33
|
+
when 'ms'
|
34
|
+
#update timer
|
35
|
+
@redis.rpush(timer_key_name(key), "#{fields[0]}:#{Time.now.to_i}")
|
36
|
+
@redis.ltrim(timer_key_name(key), 10000)
|
37
|
+
else
|
38
|
+
# invalid update
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def unbind
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rstatsd
|
2
|
+
attr_accessor :configuration
|
3
|
+
|
4
|
+
def self.configure
|
5
|
+
configuration ||= Configuration.new()
|
6
|
+
yield(configuration)
|
7
|
+
@configuration = configuration
|
8
|
+
end
|
9
|
+
|
10
|
+
class Configuration
|
11
|
+
attr_accessor :redis_db, :redis_host
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@redis_db = 1
|
15
|
+
@redis_host = '127.0.0.1:6379'
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](option)
|
19
|
+
send(option)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'hiredis'
|
3
|
+
|
4
|
+
module Rstatsd
|
5
|
+
module Helpers
|
6
|
+
def format_key(key)
|
7
|
+
key.strip
|
8
|
+
.gsub(/\s+/, '_')
|
9
|
+
.gsub(/\//, '-')
|
10
|
+
.gsub(/[^a-zA-Z_\-0-9\.]/, '')
|
11
|
+
end
|
12
|
+
|
13
|
+
def counter_key_name(key)
|
14
|
+
"counter:#{key}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def timer_key_name(key)
|
18
|
+
"timer:#{key}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def redis
|
22
|
+
@redis ||= Redis.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch_counters(counters)
|
26
|
+
finished_data = {}
|
27
|
+
counters.each_with_index do |counter, index|
|
28
|
+
data = redis_data_for(counter)
|
29
|
+
data.keys.each do |key|
|
30
|
+
finished_data[key] ||= Array.new(counters.length, 0)
|
31
|
+
finished_data[key][index] = data[key]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
finished_data
|
35
|
+
end
|
36
|
+
|
37
|
+
def redis_data_for(key)
|
38
|
+
redis.lrange(counter_key_name(key), 0, -1).inject({}) do |memo, point|
|
39
|
+
val, time = point.split(":")
|
40
|
+
memo[time] = val.to_i
|
41
|
+
memo
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'em-hiredis'
|
2
|
+
require 'eventmachine_httpserver'
|
3
|
+
require 'evma_httpserver/response'
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
require_relative './helpers'
|
7
|
+
require_relative './chart'
|
8
|
+
|
9
|
+
module Rstatsd
|
10
|
+
class Server < EventMachine::Connection
|
11
|
+
include EventMachine::HttpServer
|
12
|
+
include Rstatsd::Helpers
|
13
|
+
|
14
|
+
def post_init
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_http_request
|
19
|
+
response = EM::DelegatedHttpResponse.new(self)
|
20
|
+
|
21
|
+
case @http_request_uri
|
22
|
+
when '/'
|
23
|
+
response.content_type 'text/html'
|
24
|
+
response.content = File.open('templates/index.html').read
|
25
|
+
response.send_response
|
26
|
+
when '/stats'
|
27
|
+
Rstatsd::Chart.new(@http_query_string).draw_chart do |chart|
|
28
|
+
@chart = chart
|
29
|
+
google_chart = ERB.new(File.open('templates/google_chart.erb').read).result(binding)
|
30
|
+
|
31
|
+
response.content_type 'text/html'
|
32
|
+
response.content = google_chart
|
33
|
+
response.send_response
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/rstatsd.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/rstatsd/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Whoahbot!"]
|
6
|
+
gem.email = ["whoahbot@gmail.com"]
|
7
|
+
gem.description = %q{a stats daemon that stores data in redis}
|
8
|
+
gem.summary = %q{rstatsd is a simpler ruby implementaiton of statsd, storing the data in redis}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.bindir = 'bin'
|
13
|
+
gem.files = `git ls-files`.split("\n")
|
14
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
gem.name = "rstatsd"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = Rstatsd::VERSION
|
18
|
+
|
19
|
+
gem.add_dependency "redis"
|
20
|
+
gem.add_dependency "hiredis"
|
21
|
+
gem.add_dependency "eventmachine"
|
22
|
+
gem.add_dependency "eventmachine_httpserver"
|
23
|
+
|
24
|
+
gem.add_development_dependency "rspec"
|
25
|
+
gem.add_development_dependency "timecop"
|
26
|
+
gem.add_development_dependency "statsd-ruby"
|
27
|
+
end
|
data/spec/benchmark.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rstatsd::Chart do
|
4
|
+
let(:new_chart) {
|
5
|
+
Rstatsd::Chart.new('target=crumdinglers')
|
6
|
+
}
|
7
|
+
|
8
|
+
describe "query string parsing" do
|
9
|
+
context "for one chart target" do
|
10
|
+
it "should find the data requested from the target parameters" do
|
11
|
+
Rstatsd::Chart.new("target=crumdinglers").targets.should == ['crumdinglers']
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "for multiple chart targets" do
|
16
|
+
it "should find the data requested from the target parameters" do
|
17
|
+
Rstatsd::Chart.new("target=crumdinglers&target=snozzblers").targets.should == ['crumdinglers', 'snozzblers']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#title" do
|
23
|
+
let(:new_chart) {
|
24
|
+
Rstatsd::Chart.new('target=crumdinglers')
|
25
|
+
}
|
26
|
+
|
27
|
+
context "when no title is provided" do
|
28
|
+
it "should set the default title to be the target when none is given" do
|
29
|
+
new_chart.title.should == "Crumdinglers"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when a title is provided in the options hash" do
|
34
|
+
it "should return the title" do
|
35
|
+
Rstatsd::Chart.new('target=crumdinglers&title=Number%20of%20Crumdinglers').title.should ==
|
36
|
+
"Number of Crumdinglers"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#width" do
|
42
|
+
context "when no width is specified" do
|
43
|
+
it "should default to 800" do
|
44
|
+
new_chart.width.should == 800
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#height" do
|
50
|
+
context "when no height is specified" do
|
51
|
+
it "should default to 480" do
|
52
|
+
new_chart.height.should == 480
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#column_types" do
|
58
|
+
context "with one target" do
|
59
|
+
it "should return an array of column types and labels that describe the data" do
|
60
|
+
new_chart.column_types.should ==
|
61
|
+
[['datetime', 'Timestamp'],
|
62
|
+
['number', 'Crumdinglers']]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "with multiple targets" do
|
67
|
+
it "should only include one timestamp column" do
|
68
|
+
Rstatsd::Chart.new('target=crumdinglers&target=crambizzlers').
|
69
|
+
column_types.should == [
|
70
|
+
['datetime', 'Timestamp'],
|
71
|
+
['number', 'Crumdinglers'],
|
72
|
+
['number', 'Crambizzlers']
|
73
|
+
]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|