counter_server 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 +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/counter_server.gemspec +25 -0
- data/lib/counter_server/version.rb +3 -0
- data/lib/counter_server.rb +130 -0
- metadata +98 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "counter_server/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "counter_server"
|
|
7
|
+
s.version = CounterServer::VERSION
|
|
8
|
+
s.authors = ["Jason Katz-Brown"]
|
|
9
|
+
s.email = ["jasonkb@airbnb.com"]
|
|
10
|
+
s.homepage = ""
|
|
11
|
+
s.summary = %q{Counter Server -- listens for statsd-like counts and aggregates them in Redis}
|
|
12
|
+
s.description = %q{Count things, saves in Redis}
|
|
13
|
+
|
|
14
|
+
s.rubyforge_project = "counter_server"
|
|
15
|
+
|
|
16
|
+
s.files = `git ls-files`.split("\n")
|
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
# specify any dependencies here; for example:
|
|
22
|
+
# s.add_development_dependency "rspec"
|
|
23
|
+
s.add_runtime_dependency "eventmachine"
|
|
24
|
+
s.add_runtime_dependency "redis"
|
|
25
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Simple UDP server for aggregating counts of events and pushing them into
|
|
2
|
+
# Redis.
|
|
3
|
+
#
|
|
4
|
+
# Inspiration and code borrowed from
|
|
5
|
+
# https://github.com/quasor/statsd
|
|
6
|
+
# and
|
|
7
|
+
# https://github.com/fetep/ruby-statsd
|
|
8
|
+
|
|
9
|
+
require "eventmachine"
|
|
10
|
+
require "logger"
|
|
11
|
+
require "rubygems"
|
|
12
|
+
require "socket"
|
|
13
|
+
require 'redis'
|
|
14
|
+
|
|
15
|
+
module StatsD
|
|
16
|
+
@@counters = Hash.new { |h, k| h[k] = 0 }
|
|
17
|
+
@@logger = Logger.new(STDERR)
|
|
18
|
+
@@logger.progname = File.basename($0)
|
|
19
|
+
@@backend = nil
|
|
20
|
+
|
|
21
|
+
def self.logger
|
|
22
|
+
@@logger
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.initialize_redis_backend(options)
|
|
26
|
+
@@backend = RedisBackend.new(Redis.new(
|
|
27
|
+
:host => options.host, :port => options.port, :db => options.db))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def receive_data(msg)
|
|
31
|
+
msg.split("\n").each do |row|
|
|
32
|
+
bits = row.split(':')
|
|
33
|
+
|
|
34
|
+
if bits.size < 2
|
|
35
|
+
raise "Malformed message: #{msg}"
|
|
36
|
+
next
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
key = bits.shift
|
|
40
|
+
bits.each do |record|
|
|
41
|
+
sample_rate = 1
|
|
42
|
+
fields = record.split("|")
|
|
43
|
+
|
|
44
|
+
if fields.size < 2
|
|
45
|
+
@@logger.error "Malformed message: #{msg}"
|
|
46
|
+
next
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if (fields[1].strip == "ms")
|
|
50
|
+
@@logger.error "Timer updates not supported"
|
|
51
|
+
else
|
|
52
|
+
if (fields[2] && fields[2].match(/^@([\d\.]+)/))
|
|
53
|
+
sample_rate = fields[2].match(/^@([\d\.]+)/)[1]
|
|
54
|
+
end
|
|
55
|
+
@@counters[key] += (fields[0].to_i || 1) * (1.0 / sample_rate.to_f)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.flush
|
|
62
|
+
@@backend.flush do |store|
|
|
63
|
+
@@counters.each do |key, increment_by|
|
|
64
|
+
super_key, sub_key = key.split('.', 2)
|
|
65
|
+
if sub_key.nil?:
|
|
66
|
+
sub_key = super_key
|
|
67
|
+
super_key = 'c'
|
|
68
|
+
end
|
|
69
|
+
restored_sub_key = sub_key.gsub(/;COLON;/, ':').gsub(/;PERIOD;/, '.')
|
|
70
|
+
restored_super_key = super_key.gsub(/;COLON;/, ':').gsub(/;PERIOD;/, '.')
|
|
71
|
+
store.increment_by(super_key, sub_key, increment_by)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
@@counters.clear
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class RedisBackend
|
|
80
|
+
def initialize(redis_client)
|
|
81
|
+
@redis_client = redis_client
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def retrieve_counts(group_name)
|
|
85
|
+
ret = ActiveSupport::OrderedHash.new
|
|
86
|
+
|
|
87
|
+
# The return value of zrangebyscore looks like this:
|
|
88
|
+
# ["key 1", "1", "key 2", "4", "key 3", "100"]
|
|
89
|
+
counts_array = @redis_client.zrangebyscore(group_name, '-inf', '+inf', :with_scores => true)
|
|
90
|
+
counts_array.reverse.each_slice(2) do |pageviews, key|
|
|
91
|
+
ret[key] = pageviews.to_i
|
|
92
|
+
end
|
|
93
|
+
ret
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def flush
|
|
97
|
+
@redis_client.pipelined do
|
|
98
|
+
yield self
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def increment_by(group_name, key, increment_by)
|
|
103
|
+
@redis_client.zincrby(group_name, increment_by, key)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# In-memory backend useful for testing.
|
|
108
|
+
class InMemoryBackend
|
|
109
|
+
def initialize
|
|
110
|
+
@in_memory_hash ||= Hash.new { |h, group_name| h[group_name] = Hash.new { |h, key| h[key] = 0 } }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def retrieve_counts(group_name)
|
|
114
|
+
ret = ActiveSupport::OrderedHash.new
|
|
115
|
+
|
|
116
|
+
hash = @in_memory_hash[group_name]
|
|
117
|
+
hash.keys.sort.each do |key|
|
|
118
|
+
ret[key] = hash[key]
|
|
119
|
+
end
|
|
120
|
+
ret
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def flush
|
|
124
|
+
yield self
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def increment_by(group_name, key, increment_by)
|
|
128
|
+
@in_memory_hash[group_name][key] += increment_by
|
|
129
|
+
end
|
|
130
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: counter_server
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 29
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 0
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.0.1
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Jason Katz-Brown
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2011-12-12 00:00:00 Z
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
name: eventmachine
|
|
22
|
+
prerelease: false
|
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
none: false
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
hash: 3
|
|
29
|
+
segments:
|
|
30
|
+
- 0
|
|
31
|
+
version: "0"
|
|
32
|
+
type: :runtime
|
|
33
|
+
version_requirements: *id001
|
|
34
|
+
- !ruby/object:Gem::Dependency
|
|
35
|
+
name: redis
|
|
36
|
+
prerelease: false
|
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
38
|
+
none: false
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
hash: 3
|
|
43
|
+
segments:
|
|
44
|
+
- 0
|
|
45
|
+
version: "0"
|
|
46
|
+
type: :runtime
|
|
47
|
+
version_requirements: *id002
|
|
48
|
+
description: Count things, saves in Redis
|
|
49
|
+
email:
|
|
50
|
+
- jasonkb@airbnb.com
|
|
51
|
+
executables: []
|
|
52
|
+
|
|
53
|
+
extensions: []
|
|
54
|
+
|
|
55
|
+
extra_rdoc_files: []
|
|
56
|
+
|
|
57
|
+
files:
|
|
58
|
+
- .gitignore
|
|
59
|
+
- Gemfile
|
|
60
|
+
- Rakefile
|
|
61
|
+
- counter_server.gemspec
|
|
62
|
+
- lib/counter_server.rb
|
|
63
|
+
- lib/counter_server/version.rb
|
|
64
|
+
homepage: ""
|
|
65
|
+
licenses: []
|
|
66
|
+
|
|
67
|
+
post_install_message:
|
|
68
|
+
rdoc_options: []
|
|
69
|
+
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
hash: 3
|
|
78
|
+
segments:
|
|
79
|
+
- 0
|
|
80
|
+
version: "0"
|
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
|
+
none: false
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
hash: 3
|
|
87
|
+
segments:
|
|
88
|
+
- 0
|
|
89
|
+
version: "0"
|
|
90
|
+
requirements: []
|
|
91
|
+
|
|
92
|
+
rubyforge_project: counter_server
|
|
93
|
+
rubygems_version: 1.8.11
|
|
94
|
+
signing_key:
|
|
95
|
+
specification_version: 3
|
|
96
|
+
summary: Counter Server -- listens for statsd-like counts and aggregates them in Redis
|
|
97
|
+
test_files: []
|
|
98
|
+
|