emissary 1.3.18 → 1.3.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,130 +0,0 @@
1
- # Copyright 2010 The New York Times
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- #
15
- #
16
- #
17
- # $Id $
18
- # author: Carl P. corliss
19
- # Copyright 2009, The New York Times
20
- #
21
- require 'syslog'
22
- require 'fastthread'
23
-
24
- module Emissary
25
- class Logger
26
-
27
- LOG_SYSLOG = 0x01
28
- LOG_STDOUT = 0x02
29
- LOG_STDERR = 0x03
30
-
31
- EMERGENCY = Syslog::Constants::LOG_EMERG
32
- EMERG = Syslog::Constants::LOG_EMERG
33
- ALERT = Syslog::Constants::LOG_ALERT
34
- FATAL = Syslog::Constants::LOG_CRIT
35
- CRITICAL = Syslog::Constants::LOG_CRIT
36
- CRIT = Syslog::Constants::LOG_CRIT
37
- ERROR = Syslog::Constants::LOG_ERR
38
- ERR = Syslog::Constants::LOG_ERR
39
- WARNING = Syslog::Constants::LOG_WARNING
40
- WARN = Syslog::Constants::LOG_WARNING
41
- NOTICE = Syslog::Constants::LOG_NOTICE
42
- INFO = Syslog::Constants::LOG_INFO
43
- DEBUG = Syslog::Constants::LOG_DEBUG
44
-
45
- CONSTANT_ID_MAP = {
46
- EMERGENCY => [ :emergency, :emerg ],
47
- ALERT => [ :alert ],
48
- CRITICAL => [ :fatal, :critical, :crit ],
49
- ERROR => [ :error, :err ],
50
- WARNING => [ :warning, :warn ],
51
- NOTICE => [ :notice ],
52
- INFO => [ :info ],
53
- DEBUG => [ :debug ]
54
- }
55
-
56
- CONSTANT_NAME_MAP = CONSTANT_ID_MAP.inject({}) do |mappings,(id,names)|
57
- names.each { |name| mappings.merge!({ name => id, name.to_s.upcase => id, name.to_s.downcase => id }) }
58
- mappings
59
- end
60
-
61
- attr_accessor :mode, :level, :name
62
-
63
- def self.instance(mode = LOG_STDERR, log_level = NOTICE, name = nil)
64
- @@logger ||= Emissary::Logger.new mode, log_level
65
- end
66
-
67
- private :initialize
68
-
69
- def initialize mode, log_level, name = nil
70
- @mode = mode || LOG_STDERR
71
- @level = log_level || INFO
72
- @name = name
73
- @mutex = Mutex.new
74
- end
75
-
76
- def name=(p) @name = n || File.basename($0); end
77
-
78
- def normalize log_level
79
- return log_level unless not log_level.kind_of? Fixnum
80
- return CONSTANT_NAME_MAP[log_level.to_s.downcase] rescue INFO
81
- return INFO
82
- end
83
-
84
- def level_to_sym
85
- CONSTANT_ID_MAP[@level].first
86
- end
87
-
88
- def level_to_s
89
- level_to_sym.to_s.upcase
90
- end
91
-
92
- def loggable?(log_level)
93
- log_level <= normalize(@level)
94
- end
95
-
96
- def syslogger
97
- @mutex.synchronize {
98
- Syslog.open(name, Syslog::LOG_PID | Syslog::LOG_NDELAY, Syslog::LOG_DAEMON) do |s|
99
- s.LOG_UPTO(level)
100
- yield s
101
- end
102
- }
103
- end
104
-
105
- def log(log_level, message, *args)
106
- case @mode
107
- when Logger::LOG_SYSLOG
108
- messages = "#{CONSTANT_ID_MAP[log_level].first.to_s.upcase}: #{message}".gsub(/\t/, ' '*8).split(/\n/)
109
- # some syslog daemons have a hard time with newlines, so split into multiple lines
110
- messages.each do |message|
111
- syslogger { |s| s.log(log_level, message, *args) }
112
- end
113
- when Logger::LOG_STDOUT
114
- $stdout << sprintf(message << "\n", *args) unless not loggable? log_level
115
- when Logger::LOG_STDERR
116
- $stderr << sprintf(message << "\n", *args) unless not loggable? log_level
117
- end
118
-
119
- self
120
- end
121
-
122
- CONSTANT_ID_MAP.values.flatten.each do |log_level|
123
- class_eval %(
124
- def #{log_level}(message, *args)
125
- log(#{'::Emissary::Logger::' + log_level.to_s.upcase}, message, *args)
126
- end
127
- )
128
- end
129
- end
130
- end
@@ -1,217 +0,0 @@
1
- # Copyright 2010 The New York Times
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- #
15
- #
16
- require 'uuid'
17
- require 'bert'
18
-
19
- module Emissary
20
- class Message
21
-
22
- attr_accessor :sender, :recipient, :replyto, :errors
23
- attr_accessor :status, :operation, :thread, :time
24
- attr_accessor :account, :agent, :method, :args
25
- attr_reader :uuid, :originator
26
-
27
- def initialize(payload = {})
28
- @recipient = ''
29
- @sender = Emissary.identity.queue_name
30
- @replyto = Emissary.identity.queue_name
31
- @originator = @sender.dup
32
-
33
- @status = [ :ok, '' ] # tuple of (status type, status message)
34
- @operation = -1
35
- @thread = -1
36
- @uuid = UUID.new.generate
37
-
38
- @errors = []
39
-
40
- @agent = @method = nil
41
- @account = Emissary.identity.account_id
42
- @args = []
43
-
44
- @time = {
45
- :received => nil,
46
- :sent => nil,
47
- }
48
-
49
- payload = {
50
- :headers => {
51
- :recipient => @recipient,
52
- :sender => @sender,
53
- :replyto => @replyto,
54
- :originator => @originator,
55
- :status => @status,
56
- :operation => @operation,
57
- :thread => @thread,
58
- :uuid => @uuid,
59
- :time => @time.merge((payload[:time].symbolize rescue {})),
60
- }.merge((payload[:headers].symbolize rescue {})),
61
- :data => {
62
- :account => @account,
63
- :agent => @agent,
64
- :method => @method,
65
- :args => @args
66
- }.merge((payload[:data].symbolize! rescue {})),
67
- :errors => [ ] + (payload[:errors].symbolize rescue [])
68
- }
69
-
70
- payload[:headers].merge(payload[:data]).each do |k,v|
71
- send("#{k}=".to_sym, v) rescue nil
72
- end
73
-
74
- payload[:errors].each do |e|
75
- exception = ::Emissary.klass_const(e[:type]).new(e[:message]) rescue StandardError.new("#{e[:type]}: #{e[:message]}")
76
- exception.set_backtrace(e[:backtrace])
77
- errors << exception
78
- end
79
-
80
- @agent = @agent.to_sym rescue nil
81
- @method = @method.to_sym rescue nil
82
- @args = @args || [] rescue []
83
- end
84
-
85
- def headers()
86
- {
87
- :recipient => recipient,
88
- :sender => sender,
89
- :replyto => replyto,
90
- :originator => originator,
91
- :status => status,
92
- :operation => operation,
93
- :thread => thread,
94
- :time => time,
95
- :uuid => uuid
96
- }
97
- end
98
-
99
- def data()
100
- return { :account => account, :agent => agent, :method => method, :args => args }
101
- end
102
-
103
- def errors type = :default
104
- case type
105
- when :hashes
106
- @errors.collect do |e|
107
- { :type => e.class.name, :message => e.message, :backtrace => e.backtrace }
108
- end
109
- else
110
- @errors
111
- end
112
- end
113
-
114
- def status_type=(t) status[0] = t.to_sym; end
115
- def status_type() status[0] || :ok rescue :ok; end
116
-
117
- def status_note=(n) status[1] = n; end
118
- def status_note() status[1] || '' rescue '' ; end
119
-
120
- def route who = :recipient
121
- headers[who].split(':') || [] rescue []
122
- end
123
-
124
- def routing_key who = :recipient
125
- route(who)[0] || nil rescue nil
126
- end
127
-
128
- def exchange who = :recipient
129
- [ exchange_type(who), exchange_name(who) ]
130
- end
131
-
132
- def exchange_type who = :recipient
133
- route(who)[1].to_sym || :direct rescue :direct
134
- end
135
-
136
- def exchange_name who = :recipient
137
- key, type, name = route(who) rescue [ nil, :direct, 'amq.direct' ]
138
- name || case type.to_sym
139
- when :fanout, :topic, :matches, :headers
140
- "amq.#{type}"
141
- else
142
- 'amq.direct'
143
- end
144
- rescue
145
- 'amq.direct'
146
- end
147
-
148
- def canonical_route who = :recipient
149
- "#{routing_key(who)}:#{exchange(who).join(':')}"
150
- end
151
-
152
- def will_loop?
153
- canonical_route(:recipient) == canonical_route(:originator)
154
- end
155
-
156
- def encode
157
- BERT.encode({ :headers => headers, :data => data, :errors => errors(:hashes) })
158
- end
159
-
160
- def self.decode payload
161
- begin
162
- self.new BERT.decode(payload)
163
- rescue StandardError => e
164
- raise e unless e.message =~ /bad magic/i
165
- Emissary.logger.error "Unable to decode message - maybe it wasn't encoded with BERT..?"
166
- raise ::Emissary::Error::InvalidMessageFormat, "Unable to decode message - maybe it wasn't encoded with BERT? Message: #{payload.inspect}"
167
- end
168
- end
169
-
170
- def stamp_sent!
171
- time[:sent] = Time.now.to_f
172
- self
173
- end
174
-
175
- def stamp_received!
176
- time[:received] = Time.now.to_f
177
- self
178
- end
179
-
180
- def trip_time
181
- (time[:sent].to_f - time[:received].to_f rescue 0.0) || 0.0
182
- end
183
-
184
- def response
185
- header = {
186
- :recipient => replyto || sender,
187
- :status => [ :ok, '' ],
188
- :operation => operation,
189
- :thread => thread,
190
- :time => {
191
- :received => nil,
192
- :sent => nil
193
- }
194
- }
195
- return Message.new({ :headers => header, :data => data, :errors => errors(:hashes) })
196
- end
197
-
198
- def bounce(message = nil)
199
- message ||= 'Message failed due to missing handler.'
200
- bounced = self.response
201
- bounced.status = [ :bounced, message ]
202
- return bounced
203
- end
204
-
205
- def error(message = nil)
206
- message ||= 'Message failed due to unspecified error.'
207
- error = self.response
208
- error.status = [ :errored, message.to_s ]
209
- if message.is_a? Exception
210
- error.errors << message
211
- else
212
- ::Emissary.logger.warning "#{message.class.name} is not an exception..."
213
- end
214
- return error
215
- end
216
- end
217
- end
@@ -1,275 +0,0 @@
1
- # Copyright 2010 The New York Times
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- #
15
- #
16
- require 'monitor'
17
- require 'work_queue'
18
- require 'fastthread'
19
-
20
- module Emissary
21
- module OperatorStatistics
22
- RX_COUNT_MUTEX = Mutex.new
23
- TX_COUNT_MUTEX = Mutex.new
24
-
25
- attr_reader :rx_count, :tx_count
26
- def increment_tx_count
27
- TX_COUNT_MUTEX.synchronize {
28
- @tx_count = (@tx_count + 1 rescue 1)
29
- }
30
- end
31
-
32
- def tx_count
33
- count = 0
34
- TX_COUNT_MUTEX.synchronize {
35
- count = @tx_count
36
- @tx_count = 0
37
- }
38
- count
39
- end
40
-
41
- def increment_rx_count
42
- RX_COUNT_MUTEX.synchronize {
43
- @rx_count = (@rx_count + 1 rescue 1)
44
- }
45
- end
46
-
47
- def rx_count
48
- count = 0
49
- RX_COUNT_MUTEX.synchronize {
50
- count = @rx_count
51
- @rx_count = 0
52
- }
53
- count
54
- end
55
- end
56
-
57
- class Operator
58
- include Emissary::OperatorStatistics
59
-
60
- DEFAULT_STATUS_INTERVAL = 3600
61
- DEFAULT_MAX_WORKERS = 50
62
- MAX_WORKER_TTL = 60
63
-
64
- attr_reader :config, :shutting_down, :signature
65
-
66
- # Override .new so subclasses don't have to call super and can ignore
67
- # connection-specific arguments
68
- #
69
- def self.new(config, *args)
70
- allocate.instance_eval do
71
- # Store signature
72
- @signature = config[:signature]
73
-
74
- # Call a superclass's #initialize if it has one
75
- initialize(config, *args)
76
-
77
- # post initialize callback
78
- post_init
79
-
80
- # set signature nil
81
- @signature ||= Digest::MD5.hexdigest(config.to_s)
82
-
83
- self
84
- end
85
- end
86
-
87
- def initialize(config, *args)
88
- @config = config
89
- @workers = (args[0][:max_workers] || DEFAULT_MAX_WORKERS rescue DEFAULT_MAX_WORKERS)
90
-
91
- @agents = WorkQueue.new(@workers, nil, MAX_WORKER_TTL)
92
- @publisher = WorkQueue.new(@workers, nil, MAX_WORKER_TTL)
93
-
94
- @timer = nil
95
- @stats = WorkQueue.new(1, nil, MAX_WORKER_TTL)
96
-
97
- @rx_count = 0
98
- @tx_count = 0
99
-
100
- @shutting_down = false
101
- @connected = false
102
- end
103
-
104
- def connected?() @connected; end
105
-
106
- def post_init
107
- end
108
-
109
- def connect
110
- raise NotImplementedError, 'The connect method must be defined by the operator module'
111
- end
112
-
113
- def subscribe
114
- raise NotImplementedError, 'The subscrie method must be defined by the operator module'
115
- end
116
-
117
- def unsubscribe
118
- raise NotImplementedError, 'The unsubscribe method must be defined by the operator module'
119
- end
120
-
121
- def acknowledge message
122
- end
123
-
124
- def reject message, requeue = true
125
- end
126
-
127
- def send_data
128
- raise NotImplementedError, 'The send_data method must be defined by the operator module'
129
- end
130
-
131
- def close
132
- raise NotImplementedError, 'The close method must be defined by the operator module'
133
- end
134
-
135
- def run
136
- @connected = !!connect
137
- subscribe
138
- schedule_statistics_gatherer
139
- notify :startup
140
- connected?
141
- end
142
-
143
- def disconnect
144
- close
145
- @connected = false
146
- end
147
-
148
- def shutting_down?() @shutting_down; end
149
-
150
- def shutdown!
151
- unless shutting_down?
152
- @shutting_down = true
153
-
154
- Emissary.logger.info "Cancelling periodic timer for statistics gatherer..."
155
- @timer.cancel
156
-
157
- Emissary.logger.notice "Shutting down..."
158
- notify :shutdown
159
-
160
- Emissary.logger.info "Shutting down agent workqueue..."
161
- @agents.join
162
-
163
- Emissary.logger.info "Shutting down publisher workqueue..."
164
- @publisher.join
165
-
166
- Emissary.logger.info "Disconnecting..."
167
- disconnect
168
- end
169
- end
170
-
171
- def enabled? what
172
- unless [ :startup, :shutdown, :stats ].include? what.to_sym
173
- Emissary.logger.debug "Testing '#{what}' - it's disabled. Not a valid option."
174
- return false
175
- end
176
-
177
- unless config[what]
178
- Emissary.logger.debug "Testing '#{what}' - it's disabled. Missing from configuration."
179
- return false
180
- end
181
-
182
- if (config[:disable]||[]).include? what.to_s
183
- Emissary.logger.debug "Testing '#{what}' - it's disabled. Listed in 'disable' configuration option."
184
- return false
185
- end
186
-
187
- Emissary.logger.debug "Testing '#{what}' - it's enabled.."
188
- return true
189
- end
190
-
191
- def received message
192
- acknowledge message
193
- end
194
-
195
- def rejected message, opts = { :requeue => true }
196
- reject message, opts
197
- end
198
-
199
- def receive message
200
- @agents.enqueue_b {
201
- begin
202
- raise message.errors.first unless message.errors.empty? or not message.errors.first.is_a? Exception
203
- Emissary.logger.debug " ---> [DISPATCHER] Dispatching new message ... "
204
- Emissary.dispatch(message, config, self).activate
205
- # ack message if need be (operator dependant)
206
- received message
207
- rescue ::Emissary::Error::InvalidMessageFormat => e
208
- Emissary.logger.warning e.message
209
- rejected message, :requeue => true
210
- # if it was an encoding error, then we are done - nothing more we can do
211
- rescue Exception => e
212
- Emissary.logger.error "AgentThread Error: #{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
213
- send message.error(e)
214
- rejected message, :requeue => true
215
- else
216
- increment_rx_count
217
- end
218
- Emissary.logger.debug " ---> [DISPATCHER] tasks/workers: #{@agents.cur_tasks}/#{@agents.cur_threads}"
219
- }
220
- end
221
-
222
- def send message
223
- @publisher.enqueue_b {
224
- Emissary.logger.debug " ---> [PUBLISHER] Sending new message ... "
225
- begin
226
- unless message.will_loop?
227
- Emissary.logger.debug "[PUBLISHER] -- Sending message..."
228
- send_data message
229
- increment_tx_count
230
- else
231
- Emissary.logger.notice "Not sending message destined for myself - would loop."
232
- end
233
- rescue Exception => e
234
- Emissary.logger.error "PublisherThread Error: #{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
235
- @shutting_down = true
236
- end
237
- Emissary.logger.debug " ---> [PUBLISHER] tasks/workers: #{@publisher.cur_tasks}/#{@publisher.cur_threads}"
238
- }
239
- end
240
-
241
- def notify type
242
- return unless enabled? type and EM.reactor_running?
243
-
244
- message = Emissary::Message.new(:data => { :agent => :emissary, :method => type })
245
- case type
246
- when :startup, :shutdown
247
- message.recipient = config[type]
248
- when :stats
249
- message.agent = :stats
250
- message.method = :gather
251
- end
252
-
253
- Emissary.logger.notice "Running #{type.to_s.capitalize} Notifier"
254
- receive message
255
- end
256
-
257
- def schedule_statistics_gatherer
258
- stats_interval = enabled?(:stats) && config[:stats][:interval] ? config[:stats][:interval].to_i : DEFAULT_STATUS_INTERVAL
259
-
260
- # setup agent to process sending of messages
261
- @timer = EM.add_periodic_timer(stats_interval) do
262
- rx = rx_count; tx = tx_count
263
- rx_throughput = sprintf "%0.4f", (rx.to_f / stats_interval.to_f)
264
- tx_throughput = sprintf "%0.4f", (tx.to_f / stats_interval.to_f)
265
-
266
- Emissary.logger.notice "[statistics] publisher tasks/workers: #{@publisher.cur_tasks}/#{@publisher.cur_threads}"
267
- Emissary.logger.notice "[statistics] dispatcher tasks/workers: #{@agents.cur_tasks}/#{@agents.cur_threads}"
268
- Emissary.logger.notice "[statistics] #{tx} in #{stats_interval} seconds - tx rate: #{tx_throughput}/sec"
269
- Emissary.logger.notice "[statistics] #{rx} in #{stats_interval} seconds - rx rate: #{rx_throughput}/sec"
270
-
271
- notify :stats
272
- end
273
- end
274
- end
275
- end