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 ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in counter_server.gemspec
4
+ gemspec
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,3 @@
1
+ module CounterServer
2
+ VERSION = "0.0.1"
3
+ 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
+