flamingo 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -12,24 +12,10 @@ a try if you have the need.
12
12
 
13
13
  Dependencies
14
14
  ------------
15
- * redis
16
- * resque
17
- * sinatra
18
- * twitter-stream
19
- * yajl-ruby
20
- * active_support
21
- * redis-namespace
22
-
23
- By default, the `resque` gem installs the latest 2.x `redis` gem, so if
24
- you are using Redis 1.x, you may want to swap it out.
25
-
26
- $ gem list | grep redis
27
- redis (2.0.3)
28
- $ gem remove redis --version=2.0.3 -V
29
-
30
- $ gem install redis --version=1.0.7
31
- $ gem list | grep redis
32
- redis (1.0.7)
15
+ Check flamingo.gemspec for all the requirements. Currently there are quite a
16
+ few dependencies and they are very specific. We plan to have fewer dependencies
17
+ and be more liberal with versions soon. Right now these gems and versions are
18
+ what is working well in production for us.
33
19
 
34
20
  Getting Started
35
21
  ---------------
@@ -118,8 +104,12 @@ commandline (see below)
118
104
  Two things should now happen:
119
105
  * The pent-up jobs from the EXAMPLE queue should spray across your console
120
106
  * The resque dashboard should show the queue being emptied as a result
107
+
108
+ 10. Interact with your running flamingod instance via the REST API (by default it is on port 4711)
121
109
 
122
-
110
+ $ curl http://0.0.0.0:4711/
111
+
112
+ That will show you available resources. Also, take a look at `lib/web/server.rb` for more.
123
113
 
124
114
  Overview
125
115
  --------
@@ -157,8 +147,12 @@ code.
157
147
 
158
148
  TODO
159
149
  -----
160
- * OAuth instructions
161
-
150
+ * A proper REST API client
151
+ * Remove resque dependency
152
+ * More liberal gem dependencies
153
+ * Redis 2.x
154
+ * ActiveSupport 3.x
155
+ * OAuth support
162
156
 
163
157
  Flamingo
164
158
  --------
@@ -13,8 +13,11 @@ logging:
13
13
  level: DEBUG
14
14
 
15
15
  # Where is the redis server the flamingod processes should connect to?
16
+ # By default, all keys are namespaced wih "flamingo". May be changed
17
+ # to run multiple flamingods in the same redis DB.
16
18
  redis:
17
19
  host: 0.0.0.0:6379
20
+ namespace: flamingo
18
21
 
19
22
  # What port and interface should the flamingod web_server listen on?
20
23
  # use 0.0.0.0 for all interfaces, 127.0.0.1 to listen on only localhost
data/lib/flamingo.rb CHANGED
@@ -11,9 +11,8 @@ require 'sinatra/base'
11
11
 
12
12
  require 'flamingo/version'
13
13
  require 'flamingo/config'
14
- require 'flamingo/stats/time_series'
14
+ require 'flamingo/meta'
15
15
  require 'flamingo/dispatch_event'
16
- require 'flamingo/dispatch_error'
17
16
  require 'flamingo/stream_params'
18
17
  require 'flamingo/stream'
19
18
  require 'flamingo/subscription'
@@ -28,47 +27,48 @@ require 'flamingo/daemon/flamingod'
28
27
  require 'flamingo/logging/formatter'
29
28
  require 'flamingo/web/server'
30
29
 
31
-
32
30
  module Flamingo
33
31
 
34
32
  class << self
35
33
 
36
- def configure!(config_file=nil)
37
- config_file = find_config_file(config_file)
38
- @config = Flamingo::Config.load(config_file)
34
+ # Configures flamingo. This must be called prior to using any flamingo
35
+ # classes.
36
+ #
37
+ # The config argument may be one of:
38
+ # 1) nil: Try to locate a config file in ./flamingo.yml, ~/flamingo.yml
39
+ # 2) String: A config file name (preferred)
40
+ # 3) Flamingo::Config: Used as the configuration directly
41
+ # 4) Hash: Converted to a Flamingo::Config and used as the configuration
42
+ def configure!(cfg_info=nil,validate=true)
43
+ if cfg_info.nil? || cfg_info.kind_of?(String)
44
+ config_file = find_config_file(cfg_info)
45
+ @config = Flamingo::Config.load(config_file)
46
+ logger.info "Loaded config file from #{config_file}"
47
+ elsif cfg_info.kind_of?(Flamingo::Config)
48
+ @config = cfg_info
49
+ elsif cfg_info.kind_of?(Hash)
50
+ @config = Flamingo::Config.new(cfg_info)
51
+ end
39
52
  validate_config!
40
- logger.info "Loaded config file from #{config_file}"
41
- end
42
-
43
- def config=(config)
44
- @config = config
53
+ # Ensure redis gets loaded
54
+ redis
45
55
  end
46
56
 
47
57
  def config
48
58
  @config
49
59
  end
50
60
 
51
- # PHD: Lovingly borrowed from Resque
61
+ # PHD: Partially borrowed from resque
52
62
 
53
- # Accepts:
54
- # 1. A 'hostname:port' string
55
- # 2. A 'hostname:port:db' string (to select the Redis db)
56
- # 3. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
57
- # or `Redis::Namespace`.
63
+ # server must be a "hostname:port[:db]" string
58
64
  def redis=(server)
59
- case server
60
- when String
61
- host, port, db = server.split(':')
62
- redis = Redis.new(:host => host, :port => port,
63
- :thread_safe => true, :db => db)
64
- @redis = Redis::Namespace.new(:flamingo, :redis => redis)
65
- when Redis, Redis::Client, Redis::DistRedis
66
- @redis = Redis::Namespace.new(:flamingo, :redis => server)
67
- when Redis::Namespace
68
- @redis = server
69
- else
70
- raise "Invalid redis configuration: #{server.inspect}"
71
- end
65
+ host, port, db = server.split(':')
66
+ redis = Redis.new(:host => host, :port => port,
67
+ :thread_safe => true, :db => db)
68
+ @redis = Redis::Namespace.new(namespace, :redis => redis)
69
+
70
+ # Ensure resque is configured to use this redis as well
71
+ Resque.redis = server
72
72
  end
73
73
 
74
74
  # Returns the current Redis connection. If none has been created, will
@@ -79,28 +79,6 @@ module Flamingo
79
79
  self.redis
80
80
  end
81
81
 
82
- def new_logger
83
- # determine log file location (default is root_dir/log/flamingo.log)
84
- if valid_logging_dest?(config.logging.dest(nil))
85
- log_dest = config.logging.dest
86
- else
87
- log_dest = File.join(root_dir,'log','flamingo.log')
88
- end
89
-
90
- # determine logging level (default is Logger::INFO)
91
- begin
92
- log_level = Logger.const_get(config.logging.level.upcase)
93
- rescue
94
- log_level = Logger::INFO
95
- end
96
-
97
- # create logger facility
98
- logger = Logger.new(log_dest)
99
- logger.level = log_level
100
- logger.formatter = Flamingo::Logging::Formatter.new
101
- logger
102
- end
103
-
104
82
  def logger
105
83
  @logger ||= new_logger
106
84
  end
@@ -109,7 +87,38 @@ module Flamingo
109
87
  @logger = logger
110
88
  end
111
89
 
90
+ def namespace
91
+ config.redis.namespace(:flamingo)
92
+ end
93
+
94
+ def dispatch_queue
95
+ @dispatch_queue ||= "#{namespace}:dispatch"
96
+ end
97
+
98
+ def meta
99
+ @meta ||= Flamingo::Meta.new(redis)
100
+ end
101
+
102
+ # Intended to be called after a fork so that we don't have
103
+ # issues with shared file descriptors, sockets, etc
104
+ def reconnect!
105
+ reconnect_redis_client(@redis)
106
+ reconnect_redis_client(Resque.redis)
107
+ # Reload logger
108
+ logger.close
109
+ self.logger = new_logger
110
+ end
111
+
112
112
  private
113
+ def reconnect_redis_client(client)
114
+ # Unfortunately older versions of the Redis client don't make these
115
+ # methods public so we have to use send. Later versions have made
116
+ # these public.
117
+ if client && (client.send(:connected?) rescue true)
118
+ client.send(:reconnect)
119
+ end
120
+ end
121
+
113
122
  def root_dir
114
123
  File.expand_path(File.dirname(__FILE__)+'/..')
115
124
  end
@@ -24,8 +24,16 @@ module Flamingo
24
24
  end
25
25
 
26
26
  def start
27
- self.pid = fork { run }
28
- end
27
+ self.pid = fork do
28
+ post_fork
29
+ run
30
+ end
31
+ end
32
+
33
+ private
34
+ def post_fork
35
+ Flamingo.reconnect!
36
+ end
29
37
  end
30
38
  end
31
39
  end
@@ -2,7 +2,7 @@ module Flamingo
2
2
  module Daemon
3
3
  class DispatcherProcess < ChildProcess
4
4
  def run
5
- worker = Resque::Worker.new(:flamingo)
5
+ worker = Resque::Worker.new(Flamingo.dispatch_queue)
6
6
  def worker.procline(value)
7
7
  # Hack to get around resque insisting on setting the proces name
8
8
  $0 = "flamingod-dispatcher"
@@ -10,6 +10,6 @@ module Flamingo
10
10
  Flamingo.logger.info "Starting dispatcher on pid=#{Process.pid} under pid=#{Process.ppid}"
11
11
  worker.work(1) # Wait 1s between jobs
12
12
  end
13
- end
13
+ end
14
14
  end
15
15
  end
@@ -143,17 +143,36 @@ module Flamingo
143
143
  io.reopen '/dev/null' rescue nil
144
144
  end
145
145
  run
146
+ clear_process_meta_data
146
147
  pid_file.delete
147
148
  end
148
149
  Process.detach(pid)
149
150
  pid
150
151
  end
152
+
153
+ def set_process_meta_data
154
+ meta = Flamingo.meta
155
+ meta[:start_time] = Time.now.utc.to_i
156
+ meta[:host] = `hostname`.chomp rescue nil
157
+ meta[:pid] = Process.pid
158
+ meta[:running] = true
159
+ end
160
+
161
+ def clear_process_meta_data
162
+ meta = Flamingo.meta
163
+ meta.delete(:start_time)
164
+ meta.delete(:host)
165
+ meta.delete(:pid)
166
+ meta[:running] = false
167
+ end
151
168
 
152
169
  def run
153
170
  $0 = 'flamingod'
171
+ set_process_meta_data
154
172
  trap_signals
155
173
  start_children
156
174
  wait_on_children
175
+ clear_process_meta_data
157
176
  end
158
177
  end
159
178
  end
@@ -1,10 +1,17 @@
1
1
  module Flamingo
2
2
  class DispatchEvent
3
3
 
4
- @queue = :flamingo
5
4
  @parser = Yajl::Parser.new(:symbolize_keys => true)
6
5
 
7
6
  class << self
7
+
8
+ def queue
9
+ Flamingo.dispatch_queue
10
+ end
11
+
12
+ def meta
13
+ Flamingo.meta
14
+ end
8
15
 
9
16
  #
10
17
  # TODO Track stats including: tweets per second and last tweet time
@@ -15,10 +22,13 @@ module Flamingo
15
22
  # dispatching to improve in-order delivery (helps with "k-sorted")
16
23
  #
17
24
  def perform(event_json)
25
+ meta.incr("events:all_count")
26
+ meta.set("events:last_time",Time.now.utc.to_i)
18
27
  type, event = typed_event(parse(event_json))
28
+ meta.incr("events:#{type}_count")
19
29
  Subscription.all.each do |sub|
20
30
  Resque::Job.create(sub.name, "HandleFlamingoEvent", type, event)
21
- Flamingo.logger.debug "Put job on subscription queue #{sub.name} for #{event_json}"
31
+ Flamingo.logger.debug "Put job on subscription queue #{sub.name}\n#{event_json}"
22
32
  end
23
33
  end
24
34
 
@@ -0,0 +1,71 @@
1
+ module Flamingo
2
+
3
+ class Meta
4
+
5
+ attr_accessor :redis
6
+
7
+ def initialize(redis)
8
+ self.redis = redis
9
+ end
10
+
11
+ def incr(name,amt=1)
12
+ redis.incrby(key(name),amt)
13
+ end
14
+
15
+ def set(name,value)
16
+ redis.set(key(name),value)
17
+ end
18
+ alias_method :[]=,:set
19
+
20
+ def get(name)
21
+ norm_value(redis.get(key(name)))
22
+ end
23
+ alias_method :[],:get
24
+
25
+ def delete(name)
26
+ redis.del(key(name))
27
+ end
28
+
29
+ def all
30
+ redis.keys("#{namespace}*").map do |k|
31
+ [denamespace(k),norm_value(redis.get(k))]
32
+ end
33
+ end
34
+
35
+ def clear
36
+ all.each do |key,value|
37
+ delete(key)
38
+ end
39
+ end
40
+
41
+ def to_h
42
+ all.inject({}) do |hash, (key,value)|
43
+ hash[key] = value
44
+ hash
45
+ end
46
+ end
47
+
48
+ private
49
+ def norm_value(value)
50
+ if value.kind_of?(String) && value =~ /^\d+$/
51
+ value.to_i
52
+ else
53
+ value
54
+ end
55
+ end
56
+
57
+ def denamespace(key)
58
+ key.gsub(namespace,'')
59
+ end
60
+
61
+ def namespace
62
+ "meta:"
63
+ end
64
+
65
+ def key(name)
66
+ "#{namespace}#{name}"
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -10,6 +10,12 @@ module Flamingo
10
10
  :retweet => "statuses/retweet",
11
11
  :sample => "statuses/sample"
12
12
  )
13
+
14
+ DEFAULT_CONNECTION_OPTIONS = {
15
+ :method =>"POST",
16
+ :ssl => false,
17
+ :user_agent => "Flamingo/#{Flamingo::VERSION}"
18
+ }
13
19
 
14
20
  class << self
15
21
  def get(name)
@@ -25,13 +31,17 @@ module Flamingo
25
31
  end
26
32
 
27
33
  def connect(options)
28
- conn_opts = {:ssl => false, :user_agent => "Flamingo/0.1" }.
29
- merge(options).merge(:path=>path)
30
- Twitter::JSONStream.connect(conn_opts)
34
+ Twitter::JSONStream.connect(connection_options(options))
35
+ end
36
+
37
+ def connection_options(overrides={})
38
+ DEFAULT_CONNECTION_OPTIONS.
39
+ merge(overrides).
40
+ merge(:path=>path,:content=>query)
31
41
  end
32
42
 
33
43
  def path
34
- "/#{VERSION}/#{resource}.json?#{query}"
44
+ "/#{VERSION}/#{resource}.json"
35
45
  end
36
46
 
37
47
  def resource
@@ -1,3 +1,3 @@
1
1
  module Flamingo
2
- Version = VERSION = '0.2.1'
2
+ Version = VERSION = '0.3.1'
3
3
  end
@@ -141,7 +141,6 @@ module Flamingo
141
141
  end
142
142
 
143
143
  def dispatch_event(event_json)
144
- Flamingo.logger.debug "Wader dispatched event"
145
144
  Resque.enqueue(Flamingo::DispatchEvent, event_json)
146
145
  end
147
146
 
@@ -9,11 +9,15 @@ module Flamingo
9
9
  get '/' do
10
10
  content_type 'text/plain'
11
11
  api = self.methods.select do |method|
12
- (method =~ /^(GET|POST) /) && !(method =~ /png$/)
12
+ (method =~ /^(GET|POST|PUT|DELETE) /) && !(method =~ /png$/)
13
13
  end
14
14
  api.sort.join("\n")
15
15
  end
16
16
 
17
+ get '/meta.json' do
18
+ to_json(Flamingo.meta.to_h)
19
+ end
20
+
17
21
  get '/streams/:name.json' do
18
22
  stream = Stream.get(params[:name])
19
23
  to_json(
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flamingo
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 1
10
- version: 0.2.1
10
+ version: 0.3.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Hayes Davis
@@ -16,55 +16,63 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-10-25 00:00:00 -07:00
19
+ date: 2010-11-26 00:00:00 -08:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
- name: redis
23
+ name: activesupport
24
24
  prerelease: false
25
25
  requirement: &id001 !ruby/object:Gem::Requirement
26
26
  none: false
27
27
  requirements:
28
28
  - - ">="
29
29
  - !ruby/object:Gem::Version
30
- hash: 25
30
+ hash: 11
31
31
  segments:
32
+ - 2
32
33
  - 1
33
34
  - 0
34
- - 7
35
- version: 1.0.7
35
+ version: 2.1.0
36
+ - - <=
37
+ - !ruby/object:Gem::Version
38
+ hash: 9
39
+ segments:
40
+ - 2
41
+ - 3
42
+ - 5
43
+ version: 2.3.5
36
44
  type: :runtime
37
45
  version_requirements: *id001
38
46
  - !ruby/object:Gem::Dependency
39
- name: redis-namespace
47
+ name: eventmachine
40
48
  prerelease: false
41
49
  requirement: &id002 !ruby/object:Gem::Requirement
42
50
  none: false
43
51
  requirements:
44
- - - ">="
52
+ - - "="
45
53
  - !ruby/object:Gem::Version
46
- hash: 3
54
+ hash: 59
47
55
  segments:
48
56
  - 0
49
- - 7
50
- - 0
51
- version: 0.7.0
57
+ - 12
58
+ - 10
59
+ version: 0.12.10
52
60
  type: :runtime
53
61
  version_requirements: *id002
54
62
  - !ruby/object:Gem::Dependency
55
- name: resque
63
+ name: rack
56
64
  prerelease: false
57
65
  requirement: &id003 !ruby/object:Gem::Requirement
58
66
  none: false
59
67
  requirements:
60
- - - ">="
68
+ - - "="
61
69
  - !ruby/object:Gem::Version
62
- hash: 61
70
+ hash: 19
63
71
  segments:
64
72
  - 1
65
- - 9
66
- - 7
67
- version: 1.9.7
73
+ - 1
74
+ - 0
75
+ version: 1.1.0
68
76
  type: :runtime
69
77
  version_requirements: *id003
70
78
  - !ruby/object:Gem::Dependency
@@ -81,60 +89,146 @@ dependencies:
81
89
  - 9
82
90
  - 2
83
91
  version: 0.9.2
92
+ - - <=
93
+ - !ruby/object:Gem::Version
94
+ hash: 15
95
+ segments:
96
+ - 1
97
+ - 0
98
+ version: "1.0"
84
99
  type: :runtime
85
100
  version_requirements: *id004
86
101
  - !ruby/object:Gem::Dependency
87
- name: twitter-stream
102
+ name: redis
88
103
  prerelease: false
89
104
  requirement: &id005 !ruby/object:Gem::Requirement
90
105
  none: false
91
106
  requirements:
92
107
  - - ">="
93
108
  - !ruby/object:Gem::Version
94
- hash: 19
109
+ hash: 25
95
110
  segments:
96
- - 0
97
111
  - 1
98
- - 4
99
- version: 0.1.4
112
+ - 0
113
+ - 7
114
+ version: 1.0.7
115
+ - - <
116
+ - !ruby/object:Gem::Version
117
+ hash: 15
118
+ segments:
119
+ - 2
120
+ - 0
121
+ - 0
122
+ version: 2.0.0
100
123
  type: :runtime
101
124
  version_requirements: *id005
102
125
  - !ruby/object:Gem::Dependency
103
- name: yajl-ruby
126
+ name: redis-namespace
104
127
  prerelease: false
105
128
  requirement: &id006 !ruby/object:Gem::Requirement
106
129
  none: false
107
130
  requirements:
108
- - - ">="
131
+ - - "="
109
132
  - !ruby/object:Gem::Version
110
- hash: 9
133
+ hash: 3
111
134
  segments:
112
135
  - 0
113
- - 6
114
136
  - 7
115
- version: 0.6.7
137
+ - 0
138
+ version: 0.7.0
116
139
  type: :runtime
117
140
  version_requirements: *id006
118
141
  - !ruby/object:Gem::Dependency
119
- name: activesupport
142
+ name: resque
120
143
  prerelease: false
121
144
  requirement: &id007 !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - "="
148
+ - !ruby/object:Gem::Version
149
+ hash: 61
150
+ segments:
151
+ - 1
152
+ - 9
153
+ - 7
154
+ version: 1.9.7
155
+ type: :runtime
156
+ version_requirements: *id007
157
+ - !ruby/object:Gem::Dependency
158
+ name: twitter-stream
159
+ prerelease: false
160
+ requirement: &id008 !ruby/object:Gem::Requirement
122
161
  none: false
123
162
  requirements:
124
163
  - - ">="
125
164
  - !ruby/object:Gem::Version
126
- hash: 11
165
+ hash: 19
127
166
  segments:
128
- - 2
167
+ - 0
129
168
  - 1
169
+ - 4
170
+ version: 0.1.4
171
+ - - <=
172
+ - !ruby/object:Gem::Version
173
+ hash: 23
174
+ segments:
130
175
  - 0
131
- version: 2.1.0
176
+ - 1
177
+ - 6
178
+ version: 0.1.6
132
179
  type: :runtime
133
- version_requirements: *id007
180
+ version_requirements: *id008
181
+ - !ruby/object:Gem::Dependency
182
+ name: yajl-ruby
183
+ prerelease: false
184
+ requirement: &id009 !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - "="
188
+ - !ruby/object:Gem::Version
189
+ hash: 15
190
+ segments:
191
+ - 0
192
+ - 7
193
+ - 6
194
+ version: 0.7.6
195
+ type: :runtime
196
+ version_requirements: *id009
197
+ - !ruby/object:Gem::Dependency
198
+ name: SystemTimer
199
+ prerelease: false
200
+ requirement: &id010 !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ~>
204
+ - !ruby/object:Gem::Version
205
+ hash: 11
206
+ segments:
207
+ - 1
208
+ - 2
209
+ version: "1.2"
210
+ type: :runtime
211
+ version_requirements: *id010
212
+ - !ruby/object:Gem::Dependency
213
+ name: mocha
214
+ prerelease: false
215
+ requirement: &id011 !ruby/object:Gem::Requirement
216
+ none: false
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ hash: 43
221
+ segments:
222
+ - 0
223
+ - 9
224
+ - 8
225
+ version: 0.9.8
226
+ type: :development
227
+ version_requirements: *id011
134
228
  - !ruby/object:Gem::Dependency
135
229
  name: mockingbird
136
230
  prerelease: false
137
- requirement: &id008 !ruby/object:Gem::Requirement
231
+ requirement: &id012 !ruby/object:Gem::Requirement
138
232
  none: false
139
233
  requirements:
140
234
  - - ">="
@@ -146,8 +240,8 @@ dependencies:
146
240
  - 0
147
241
  version: 0.1.0
148
242
  type: :development
149
- version_requirements: *id008
150
- description: " Flamingo makes it easy to wade through the Twitter Streaming API by \n handling all connectivity and resource management for you. You just tell \n it what to track and consume the information in a resque queue. \n\n Flamingo isn't a traditional ruby gem. You don't require it into your code.\n Instead, it's designed to run as a daemon like redis or mysql. It provides \n a REST interface to change the parameters sent to the Twitter Streaming \n resource. All events from the streaming API are placed on a resque job \n queue where your application can process them.\n\n"
243
+ version_requirements: *id012
244
+ description: " Flamingo makes it easy to wade through the Twitter Streaming API by \n handling all connectivity and resource management for you. You just tell \n it what to track and consume the information in a resque queue. \n\n Flamingo isn't a traditional ruby gem. You don't require it into your code.\n Instead, it's designed to run as a daemon like redis or mysql. It provides \n a REST interface to change the parameters sent to the Twitter Streaming \n resource. All events from the streaming API are placed on a resque job \n queue where your application can process them.\n \n CAVEAT EMPTOR: This gem is alpha code so act accordingly.\n\n"
151
245
  email: hayes@appozite.com
152
246
  executables:
153
247
  - flamingo
@@ -168,10 +262,9 @@ files:
168
262
  - lib/flamingo/daemon/trap_keeper.rb
169
263
  - lib/flamingo/daemon/wader_process.rb
170
264
  - lib/flamingo/daemon/web_server_process.rb
171
- - lib/flamingo/dispatch_error.rb
172
265
  - lib/flamingo/dispatch_event.rb
173
266
  - lib/flamingo/logging/formatter.rb
174
- - lib/flamingo/stats/time_series.rb
267
+ - lib/flamingo/meta.rb
175
268
  - lib/flamingo/stream.rb
176
269
  - lib/flamingo/stream_params.rb
177
270
  - lib/flamingo/subscription.rb
@@ -1,11 +0,0 @@
1
- module Flamingo
2
- class DispatchError
3
-
4
- @queue = :flamingo
5
-
6
- def self.perform(type,message,data)
7
- Flamingo.logger.info("#{type}, #{message}, #{data.inspect}\n")
8
- end
9
-
10
- end
11
- end
@@ -1,134 +0,0 @@
1
- module Flamingo
2
- module Stats
3
- class TimeSeries
4
-
5
- attr_accessor :name, :seconds_per_tick, :total_ticks
6
-
7
- class << self
8
- def create(name,seconds_per_tick,total_ticks)
9
- redis.set("stats:#{name}:schema","#{seconds_per_tick},#{total_ticks}")
10
- new(name,seconds_per_tick,total_ticks)
11
- end
12
-
13
- def get(name)
14
- schema_value = redis.get("stats:#{name}:schema")
15
- raise "No schema specified" unless schema_value
16
- seconds_per_tick, total_ticks = schema_value.split(",").map{|v| v.to_i}
17
- new(name,seconds_per_tick,total_ticks)
18
- end
19
-
20
- def redis
21
- Flamingo.redis
22
- end
23
-
24
- end
25
-
26
- def initialize(name,seconds_per_tick,total_ticks)
27
- self.name = name
28
- self.seconds_per_tick = seconds_per_tick
29
- self.total_ticks = total_ticks
30
- end
31
-
32
- def data(ticks=total_ticks,time=Time.now.utc)
33
- range = tick_range(ticks,time)
34
- keys = range.map {|t| value_key(t) }
35
- values = redis.mget(keys).map {|v| (v || 0).to_f }
36
- range.map {|t| t * seconds_per_tick }.zip(values)
37
- end
38
-
39
- def each_datum(ticks=total_ticks,time=Time.now.utc)
40
- tick_range(ticks,time).each do |tick|
41
- yield(tick*seconds_per_tick,value(tick))
42
- end
43
- end
44
-
45
- def tick_range(ticks=total_ticks,time=Time.now.utc)
46
- end_tick = to_tick(time)
47
- start_tick = (end_tick - ticks) + 1
48
- (start_tick..end_tick)
49
- end
50
-
51
- def average(ticks=total_ticks,time=Time.now.utc)
52
- count = 0
53
- sum = 0
54
- each_datum do |ts, value|
55
- count += 1
56
- sum += value
57
- end
58
- if count == 0
59
- nil
60
- else
61
- sum / count
62
- end
63
- end
64
-
65
- def increment(val=1,time=Time.now.utc)
66
- modify_value_at(time) do |val_key|
67
- redis.incrby(val_key,val)
68
- end
69
- end
70
-
71
- def set(val,time=Time.now.utc)
72
- modify_value_at(time) do |val_key|
73
- redis.set(val_key,val)
74
- end
75
- end
76
-
77
- private
78
- def modify_value_at(time=Time.now.utc)
79
- t = to_tick(time)
80
- return false unless valid?(t)
81
- val_key = value_key(t)
82
- yield(val_key)
83
- ver_key = version_key(t)
84
- prev_tick = redis.getset(ver_key,t)
85
- if prev_tick && prev_tick.to_i < t
86
- redis.del(value_key(prev_tick.to_i))
87
- end
88
- true
89
- end
90
-
91
- def value(tick)
92
- val = redis.get(value_key(tick))
93
- val ? val.to_f : 0
94
- end
95
-
96
- def to_tick(time=Time.now.utc)
97
- (time.to_i / seconds_per_tick)
98
- end
99
-
100
- def now_tick
101
- to_tick(Time.now.utc)
102
- end
103
-
104
- def valid?(tick)
105
- nt = now_tick
106
- (nt - total_ticks) <= tick && tick <= nt
107
- end
108
-
109
- def tick_series(tick)
110
- tick / total_ticks
111
- end
112
-
113
- def tick_pos(tick)
114
- tick % total_ticks
115
- end
116
-
117
- def value_key(tick)
118
- s = tick_series(tick)
119
- pos = tick_pos(tick)
120
- "stats:#{name}:#{s}:#{pos}"
121
- end
122
-
123
- def version_key(tick)
124
- pos = tick_pos(tick)
125
- "stats:#{name}:v:#{pos}"
126
- end
127
-
128
- def redis
129
- self.class.redis
130
- end
131
-
132
- end
133
- end
134
- end