emissary 1.3.20 → 1.3.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,96 @@
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
+ module Emissary
17
+ class Agent::Stats < Agent
18
+ STATISTIC_TYPES = [ :cpu, :network, :disk ]
19
+
20
+ begin
21
+ require 'sys/cpu'
22
+ rescue LoadError
23
+ STATISTIC_TYPES.delete(:cpu)
24
+ ::Emissary.logger.warning "Ruby Gem 'sys-cpu' doesn't appear to be present - removing statistic gather for cpu."
25
+ end
26
+
27
+ begin
28
+ require 'ifconfig'
29
+ rescue LoadError
30
+ STATISTIC_TYPES.delete(:network)
31
+ ::Emissary.logger.warning "Ruby Gem 'ifconfig' doesn't appear to be present - removing statistic gather for network."
32
+ end
33
+
34
+ def valid_methods
35
+ [ :gather ]
36
+ end
37
+
38
+ def gather
39
+ message.recipient = "#{config[:stats][:queue_base]}:#{message.exchange_type.to_s}"
40
+ message.args = STATISTIC_TYPES.inject([]) do |args, type|
41
+ unless (data = self.__send__(type)).nil?
42
+ args << { type => data }
43
+ end
44
+ args
45
+ end
46
+
47
+ throw :skip_implicit_response unless not message.args.empty?
48
+ return message
49
+ end
50
+
51
+ def disk
52
+ @cmd = "/usr/bin/env df -B K -P -T -x devfs -x tmpfs | /usr/bin/env tail -n +2"
53
+
54
+ data = IO.popen(@cmd){ |f| f.readlines }.collect { |l| l.split(/\s+/) }
55
+ data.inject([]) { |data,line|
56
+ device = Hash[[:device, :type, :size, :used, :avail, :percent, :mount].zip(line.collect!{|v| v =~ /^\d+/ ? v[/^(\d+)/].to_i : v })]
57
+
58
+ ::Emissary.logger.notice("[statistics] Disk#%s: type:%s mount:%s size:%d used:%d in-use:%d%%",
59
+ device[:device], device[:type], device[:mount], device[:size], device[:used], device[:percent]
60
+ )
61
+
62
+ data << device
63
+ }
64
+ end
65
+
66
+ def cpu
67
+ load_average = Sys::CPU.load_avg
68
+ ::Emissary.logger.notice "[statistics] CPU: #{load_average.join ', '}"
69
+ load_average
70
+ end
71
+
72
+ def network
73
+ interfaces = (ifconfig = IfconfigWrapper.new.parse).interfaces.inject([]) do |interfaces, name|
74
+ interfaces << (interface = {
75
+ :name => name,
76
+ :tx => ifconfig[name].tx.symbolize,
77
+ :rx => ifconfig[name].rx.symbolize,
78
+ :up => ifconfig[name].status,
79
+ :ips => ifconfig[name].addresses('inet').collect { |ip| ip.to_s }
80
+ })
81
+
82
+ ::Emissary.logger.notice("[statistics] Network#%s: state:%s tx:%d rx:%d inet:%s",
83
+ name,
84
+ (interface[:up] ? 'up' : 'down'),
85
+ interface[:tx][:bytes],
86
+ interface[:rx][:bytes],
87
+ interface[:ips].join(',')
88
+ ) unless interface.try(:[], :tx).nil?
89
+
90
+ interfaces
91
+ end
92
+
93
+ return interfaces
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,40 @@
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
+ module Emissary
17
+ class Agent::Test < Agent
18
+ def valid_methods
19
+ [:test_raise]
20
+ end
21
+
22
+ def test_raise klass, *args
23
+ ::Emissary.logger.debug "TEST AGENT: #test(#{klass}, #{args.inspect})"
24
+
25
+ exception = nil
26
+ begin
27
+ e_klass = ::Emissary.klass_const(klass)
28
+ unless not e_klass.try(:new).try(:is_a?, Exception)
29
+ raise e_klass, *args
30
+ else
31
+ raise Exception, "#{e_klass.name.to_s rescue e_klass.to_s} is not a valid exception name!"
32
+ end
33
+ rescue Exception => e
34
+ exception = e
35
+ end
36
+
37
+ message.error exception
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,231 @@
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 'inifile'
17
+
18
+ #
19
+ # This class represents the INI file and can be used to parse INI files.
20
+ # Derived from IniFile gem, found on http://rubyforge.org/projects/inifile/
21
+ #
22
+ module Emissary
23
+ class ConfigParseError < ::Emissary::Error
24
+ def initialize(message)
25
+ super(Exception, message)
26
+ end
27
+ end
28
+
29
+ class ConfigValidationError < ::Emissary::Error
30
+ def initialize(message)
31
+ super(Exception, message)
32
+ end
33
+ end
34
+
35
+ class ConfigFile < IniFile
36
+
37
+ attr_reader :ini
38
+ def initialize( filename, opts = {} )
39
+ @line_number = 0
40
+ @fn = filename
41
+ @comment = opts[:comment] || '#'
42
+ @param = opts[:parameter] || '='
43
+ @debug = !!opts[:debug]
44
+ @ini = Hash.new {|h,k| h[k] = Hash.new}
45
+
46
+ @rgxp_comment = /^\s*$|^\s*[#{@comment}]/
47
+ @rgxp_section = /^\s*\[([^\]]+)\]/
48
+ @rgxp_param = /^([^#{@param}]+)#{@param}(.*)$/
49
+
50
+ @rgxp_dict_start = /^([^#{@param}]+)#{@param}\s*\{\s*$/
51
+ @rgxp_dict_stop = /^\s*\}\s*$/
52
+ @dict_stack = []
53
+
54
+ @rgxp_list_start = /^([^#{@param}]+)#{@param}\s*\[\s*$/
55
+ @rgxp_list_line = /^([^#{@param}]+)#{@param}\s*\[\s*([^\]]+)\]\s*$/
56
+ @rgxp_list_stop = /^\s*\]\s*$/
57
+ @list_items = []
58
+ @in_list_name = nil
59
+
60
+ super filename, opts
61
+
62
+ yield self if block_given?
63
+ end
64
+
65
+ #
66
+ # call-seq:
67
+ # ini_file[section]
68
+ #
69
+ # Get the hash of parameter/value pairs for the given _section_.
70
+ #
71
+ def []( section )
72
+ return nil if section.nil?
73
+ @ini[section.to_sym]
74
+ end
75
+
76
+ #
77
+ # call-seq:
78
+ # has_section?( section )
79
+ #
80
+ # Returns +true+ if the named _section_ exists in the INI file.
81
+ #
82
+ def has_section?( section )
83
+ @ini.has_key? section.to_sym
84
+ end
85
+
86
+ #
87
+ # call-seq:
88
+ # parse
89
+ #
90
+ # Loops over each line of the file, passing it off to the parse_line method
91
+ #
92
+ def parse
93
+ return unless ::Kernel.test ?f, @fn
94
+ @section_name = nil
95
+ ::File.open(@fn, 'r') do |f|
96
+ while line = f.gets
97
+ @line_number += 1
98
+ parse_line line.chomp
99
+ end
100
+ end
101
+ @section_name = nil
102
+ @line_number = 0
103
+ return
104
+ end
105
+
106
+ #
107
+ # call-seq:
108
+ # set_vall( key, value) => value
109
+ #
110
+ # Sets the value of the given key taking the current stack level into account
111
+ #
112
+ def set_value key, value
113
+ begin
114
+ p = @ini[@section_name]
115
+ @dict_stack.map { |d| p = (p[d]||={}) }
116
+ p[key] = value
117
+ rescue NoMethodError
118
+ raise ConfigParseError, "sectionless parameter declaration encountered at line #{@line_number}"
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ #
125
+ # call-seq:
126
+ # current_state (param = nil) => state
127
+ #
128
+ # Used for outputing the current parameter hash heirarchy in debug mode
129
+ #
130
+ def current_state param = nil
131
+ state = "@ini[:#{@section_name}]"
132
+ state << @dict_stack.collect { |c| "[:#{c}]" }.join unless @dict_stack.empty?
133
+ state << "[:#{@in_list_name}]" unless @in_list_name.nil?
134
+ state << "[:#{param}]" unless param.nil?
135
+ state
136
+ end
137
+
138
+ #
139
+ # call-seq:
140
+ # parse_line(line)
141
+ #
142
+ # Parses the given line
143
+ #
144
+ def parse_line line
145
+ line.gsub!(/\s+#.*$/, '') # strip comments
146
+
147
+ # replace __FILE__ with the file being parsed
148
+ line.gsub!('__FILE__', File.expand_path(@fn))
149
+
150
+ # replace __DIR__ with the path of the file being parsed
151
+ line.gsub!('__DIR__', File.dirname(File.expand_path(@fn)))
152
+
153
+ # replace __ID_<METHOD>__ with Emissary.identity.<method>
154
+ [ :name, :instance_id, :server_id, :cluster_id, :account_id ].each do |id_method|
155
+ line.gsub!("__ID_#{id_method.to_s.upcase}__", Emissary.identity.__send__(id_method).to_s)
156
+ end
157
+
158
+ if not @in_list_name.nil? and line !~ @rgxp_list_stop
159
+ line = line.strip.split(/\s*,\s*/).compact.reject(&:blank?)
160
+ Emissary.logger.debug " ---> LIST ITEM #{current_state} << #{line.inspect}" if @debug
161
+ # then we're in the middle of a list item, so add to it
162
+ @list_items = @list_items | line
163
+ return
164
+ end
165
+
166
+ case line
167
+ # ignore blank lines and comment lines
168
+ when @rgxp_comment: return
169
+
170
+ # this is a section declaration
171
+ when @rgxp_section
172
+ Emissary.logger.debug "SECTION: #{line}" if @debug
173
+
174
+ unless @in_dict_name.nil?
175
+ raise ConfigParseError, "dictionary '#{@in_dict_name}' crosses section '#{$1.strip.downcase}' boundary at line #{@line_number}"
176
+ end
177
+
178
+ @section_name = $1.strip.downcase.to_sym
179
+ @ini[@section_name] ||= {}
180
+
181
+ when @rgxp_dict_start
182
+ @dict_stack << $1.strip.downcase.to_sym
183
+ Emissary.logger.debug " ---> DICT_BEG: #{@dict_stack.last}" if @debug
184
+
185
+ when @rgxp_dict_stop
186
+ raise ConfigParseError, "end of dictionary found without beginning at line #{@line_number}" if @dict_stack.empty?
187
+ Emissary.logger.debug " ---> DICT_END: #{@dict_stack.last}" if @debug
188
+ @dict_stack.pop
189
+ return
190
+
191
+ when @rgxp_list_line
192
+ list_name = $1.strip.downcase.to_sym
193
+ list_items = $2.strip.split(/\s*,\s*/).compact.reject(&:blank?)
194
+
195
+ unless not @debug
196
+ Emissary.logger.debug " ---> LIST_BEG: #{list_name}"
197
+ list_items.each do |li|
198
+ Emissary.logger.debug " ---> LIST_ITEM: #{current_state list_name} << [\"#{li}\"]"
199
+ end
200
+ Emissary.logger.debug " ---> LIST_END: #{list_name}"
201
+ end
202
+
203
+ set_value list_name, list_items
204
+
205
+ when @rgxp_list_start
206
+ Emissary.logger.debug " ---> LIST_BEG: #{line}" if @debug
207
+ @in_list_name = $1.strip.downcase.to_sym
208
+
209
+ when @rgxp_list_stop
210
+ Emissary.logger.debug " ---> LIST_END: #{@in_list_name} - #{@list_items.inspect}" if @debug
211
+ raise ConfigParseError, "end of list found without beginning at line #{@line_number}" if @in_list_name.nil?
212
+ set_value @in_list_name, @list_items
213
+
214
+ @in_list_name = nil
215
+ @list_items = []
216
+
217
+ when @rgxp_param
218
+ val = $2.strip
219
+ val = val[1..-2] if val[0..0] == "'" || val[-1..-1] == '"'
220
+
221
+ key = $1.strip.downcase.to_sym
222
+ Emissary.logger.debug " ---> PARAM: #{current_state key} = #{val}" if @debug
223
+ set_value key, val
224
+
225
+ else
226
+ raise Exception, "Unable to parse line #{@line_number}: #{line}"
227
+ end
228
+ return true
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,60 @@
1
+ ##### BORROWED FROM ACTIVESUPPORT #####
2
+
3
+ class Object
4
+ # An object is blank if it's false, empty, or a whitespace string.
5
+ # For example, "", " ", +nil+, [], and {} are blank.
6
+ #
7
+ # This simplifies
8
+ #
9
+ # if !address.nil? && !address.empty?
10
+ #
11
+ # to
12
+ #
13
+ # if !address.blank?
14
+ def blank?
15
+ respond_to?(:empty?) ? empty? : !self
16
+ end
17
+
18
+ # An object is present if it's not blank.
19
+ def present?
20
+ !blank?
21
+ end
22
+ end
23
+
24
+ class NilClass #:nodoc:
25
+ def blank?
26
+ true
27
+ end
28
+ end
29
+
30
+ class FalseClass #:nodoc:
31
+ def blank?
32
+ true
33
+ end
34
+ end
35
+
36
+ class TrueClass #:nodoc:
37
+ def blank?
38
+ false
39
+ end
40
+ end
41
+
42
+ class Array #:nodoc:
43
+ alias_method :blank?, :empty?
44
+ end
45
+
46
+ class Hash #:nodoc:
47
+ alias_method :blank?, :empty?
48
+ end
49
+
50
+ class String #:nodoc:
51
+ def blank?
52
+ self !~ /\S/
53
+ end
54
+ end
55
+
56
+ class Numeric #:nodoc:
57
+ def blank?
58
+ false
59
+ end
60
+ end
@@ -0,0 +1,21 @@
1
+ ##### BORROWED FROM ACTIVESUPPORT #####
2
+
3
+ class Object
4
+ def try name, *args
5
+ self.__send__(name, *args) unless not self.respond_to? name
6
+ end
7
+
8
+ def __method__
9
+ caller[0] =~ /\d:in `([^']+)'/
10
+ $1.to_sym rescue nil
11
+ end
12
+
13
+ def __caller__
14
+ caller[1] =~ /\d:in `([^']+)'/
15
+ $1.to_sym rescue nil
16
+ end
17
+
18
+ def clone_deep
19
+ Marshal.load(Marshal.dump(self)) rescue self.clone
20
+ end
21
+ end