stella 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/{README.txt → README.textile} +63 -40
  2. data/Rakefile +7 -5
  3. data/bin/stella +1 -1
  4. data/bin/stella.bat +12 -0
  5. data/lib/pcaplet.rb +180 -0
  6. data/lib/stella/adapter/ab.rb +57 -33
  7. data/lib/stella/adapter/base.rb +11 -1
  8. data/lib/stella/adapter/httperf.rb +13 -10
  9. data/lib/stella/adapter/pcap_watcher.rb +221 -0
  10. data/lib/stella/adapter/proxy_watcher.rb +76 -0
  11. data/lib/stella/adapter/siege.rb +28 -11
  12. data/lib/stella/cli/agents.rb +2 -2
  13. data/lib/stella/cli/base.rb +37 -1
  14. data/lib/stella/cli/localtest.rb +1 -2
  15. data/lib/stella/cli/sysinfo.rb +17 -0
  16. data/lib/stella/cli/watch.rb +278 -0
  17. data/lib/stella/cli.rb +23 -11
  18. data/lib/stella/command/base.rb +1 -10
  19. data/lib/stella/command/localtest.rb +43 -23
  20. data/lib/stella/data/domain.rb +75 -0
  21. data/lib/stella/data/http.rb +124 -0
  22. data/lib/stella/logger.rb +16 -5
  23. data/lib/stella/storable.rb +4 -2
  24. data/lib/stella/support.rb +71 -0
  25. data/lib/stella/sysinfo.rb +247 -0
  26. data/lib/stella/test/base.rb +5 -1
  27. data/lib/stella/test/definition.rb +1 -1
  28. data/lib/stella/test/run/summary.rb +14 -4
  29. data/lib/stella/text/resource.rb +0 -1
  30. data/lib/stella.rb +28 -10
  31. data/lib/utils/domainutil.rb +47 -0
  32. data/lib/utils/fileutil.rb +22 -3
  33. data/lib/utils/httputil.rb +184 -128
  34. data/lib/utils/mathutil.rb +20 -7
  35. data/lib/win32/Console/ANSI.rb +305 -0
  36. data/lib/win32/Console.rb +970 -0
  37. data/spec/show-agents_spec.rb +0 -0
  38. data/support/kvm.h +91 -0
  39. data/support/ruby-pcap-takuma-notes.txt +19 -0
  40. data/support/ruby-pcap-takuma-patch.txt +30 -0
  41. data/support/text/en.yaml +26 -3
  42. data/vendor/frylock/README.textile +72 -0
  43. data/vendor/frylock/bin/example +170 -0
  44. data/vendor/frylock/frylock.gemspec +18 -0
  45. data/vendor/frylock/lib/frylock/exceptions.rb +24 -0
  46. data/vendor/frylock/lib/frylock.rb +232 -0
  47. data/vendor/frylock/test/command_test.rb +33 -0
  48. data/vendor/hitimes-0.4.0/HISTORY +28 -0
  49. data/vendor/hitimes-0.4.0/LICENSE.txt +19 -0
  50. data/vendor/hitimes-0.4.0/README +80 -0
  51. data/vendor/hitimes-0.4.0/Rakefile +63 -0
  52. data/vendor/hitimes-0.4.0/examples/benchmarks.rb +86 -0
  53. data/vendor/hitimes-0.4.0/examples/stats.rb +29 -0
  54. data/vendor/hitimes-0.4.0/ext/extconf.rb +15 -0
  55. data/vendor/hitimes-0.4.0/ext/hitimes_ext.c +21 -0
  56. data/vendor/hitimes-0.4.0/ext/hitimes_instant_clock_gettime.c +20 -0
  57. data/vendor/hitimes-0.4.0/ext/hitimes_instant_osx.c +16 -0
  58. data/vendor/hitimes-0.4.0/ext/hitimes_instant_windows.c +27 -0
  59. data/vendor/hitimes-0.4.0/ext/hitimes_interval.c +340 -0
  60. data/vendor/hitimes-0.4.0/ext/hitimes_interval.h +73 -0
  61. data/vendor/hitimes-0.4.0/ext/hitimes_stats.c +242 -0
  62. data/vendor/hitimes-0.4.0/ext/hitimes_stats.h +30 -0
  63. data/vendor/hitimes-0.4.0/ext/rbconfig-mingw.rb +178 -0
  64. data/vendor/hitimes-0.4.0/ext/rbconfig.rb +178 -0
  65. data/vendor/hitimes-0.4.0/gemspec.rb +54 -0
  66. data/vendor/hitimes-0.4.0/lib/hitimes/mutexed_stats.rb +23 -0
  67. data/vendor/hitimes-0.4.0/lib/hitimes/paths.rb +54 -0
  68. data/vendor/hitimes-0.4.0/lib/hitimes/stats.rb +29 -0
  69. data/vendor/hitimes-0.4.0/lib/hitimes/timer.rb +223 -0
  70. data/vendor/hitimes-0.4.0/lib/hitimes/version.rb +42 -0
  71. data/vendor/hitimes-0.4.0/lib/hitimes.rb +24 -0
  72. data/vendor/hitimes-0.4.0/spec/interval_spec.rb +115 -0
  73. data/vendor/hitimes-0.4.0/spec/mutex_stats_spec.rb +34 -0
  74. data/vendor/hitimes-0.4.0/spec/paths_spec.rb +14 -0
  75. data/vendor/hitimes-0.4.0/spec/spec_helper.rb +6 -0
  76. data/vendor/hitimes-0.4.0/spec/stats_spec.rb +72 -0
  77. data/vendor/hitimes-0.4.0/spec/timer_spec.rb +105 -0
  78. data/vendor/hitimes-0.4.0/spec/version_spec.rb +27 -0
  79. data/vendor/hitimes-0.4.0/tasks/announce.rake +39 -0
  80. data/vendor/hitimes-0.4.0/tasks/config.rb +107 -0
  81. data/vendor/hitimes-0.4.0/tasks/distribution.rake +53 -0
  82. data/vendor/hitimes-0.4.0/tasks/documentation.rake +33 -0
  83. data/vendor/hitimes-0.4.0/tasks/extension.rake +64 -0
  84. data/vendor/hitimes-0.4.0/tasks/rspec.rake +31 -0
  85. data/vendor/hitimes-0.4.0/tasks/rubyforge.rake +52 -0
  86. data/vendor/hitimes-0.4.0/tasks/utils.rb +80 -0
  87. data/vendor/useragent/lib/user_agent.rb +1 -1
  88. metadata +87 -8
@@ -0,0 +1,47 @@
1
+
2
+ require 'net/dns/packet'
3
+
4
+ module DomainUtil
5
+
6
+ def DomainUtil.parse_domain_request(data=[])
7
+ return unless data && !data.empty?
8
+ data = data.split(/\r?\n/) unless data.kind_of? Array
9
+ data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
10
+
11
+ dns_data = Net::DNS::Packet.parse( data.join($/) )
12
+ return unless dns_data.header.query?
13
+ domain_name = dns_data.question[0].qName
14
+ return dns_data, domain_name, dns_data.header
15
+ end
16
+
17
+ def DomainUtil.parse_domain_response(data=[])
18
+ return unless data && !data.empty?
19
+ data = data.split(/\r?\n/) unless data.kind_of? Array
20
+ data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
21
+
22
+ # This is the heavy lifting.
23
+ dns_data = Net::DNS::Packet.parse( data.join($/) )
24
+
25
+ # We don't want queries or empty answers
26
+ return if dns_data.header.query? || dns_data.answer.nil? || dns_data.answer.empty?
27
+
28
+ domain_name = dns_data.answer[0].name
29
+
30
+ # Empty the lists if they are already populated
31
+ addresses = []
32
+ cnames = []
33
+
34
+ # Store the CNAMEs associated to this domain. Can be empty.
35
+ dns_data.each_cname do |cname|
36
+ cnames << cname.to_s
37
+ end
38
+
39
+ # Store the IP address for this domain. If empty, the lookup was unsuccessful.
40
+ dns_data.each_address do |ip|
41
+ addresses << ip.to_s
42
+ end
43
+
44
+ return dns_data, domain_name, dns_data.header, addresses, cnames
45
+ end
46
+
47
+ end
@@ -1,4 +1,4 @@
1
- require 'ftools'
1
+ require 'fileutils'
2
2
 
3
3
  module FileUtil
4
4
 
@@ -50,10 +50,29 @@ module FileUtil
50
50
  File.chmod(0600, path)
51
51
  end
52
52
 
53
- def FileUtil.create_dir(dirpath)
53
+ def FileUtil.create_file(filepath, perm='w', file_perms=nil, force=false)
54
+ raise Exception.new("File #{filepath} already exists!") if File.exists?(filepath) && !force
55
+
56
+ newfile = File.new(filepath, perm)
57
+ begin
58
+ if file_perms && File.exists?(file_perms)
59
+ File.chown(File.stat(file_perms).uid.to_i, File.stat(file_perms).gid.to_i, filepath)
60
+ end
61
+ rescue NotImplementedError => ex
62
+ end
63
+
64
+ newfile
65
+ end
66
+
67
+ def FileUtil.create_dir(dirpath, dir_perms=nil)
54
68
  return if File.directory?(dirpath)
55
69
 
56
70
  #STDERR.puts "Creating #{ dirpath }"
57
- File.makedirs(dirpath)
71
+ FileUtils.makedirs(dirpath)
72
+
73
+ if dir_perms && File.exists?(dir_perms)
74
+ File.chown(File.stat(dir_perms).uid.to_i, File.stat(dir_perms).gid.to_i, dirpath)
75
+ end
76
+
58
77
  end
59
78
  end
@@ -1,172 +1,210 @@
1
- require 'net/http'
1
+
2
2
  require 'uri'
3
3
  require 'timeout'
4
+ require 'net/http'
4
5
 
5
6
  module HTTPUtil
6
7
  VALID_METHODS = %w{GET HEAD POST PUT DELETE}
7
8
  @@timeout = 20
8
9
 
9
- def HTTPUtil.hostname(tmp_uri)
10
- return if tmp_uri.empty?
11
- uri = URI.parse(tmp_uri) if tmp_uri.is_a? String
12
-
13
- #STDERR.puts "Hostname for #{ uri.port }"
14
- uri.host
10
+ # Takes a string. See WEBrick::parse_header(string).
11
+ def HTTPUtil.parse_header(raw)
12
+ header = Hash.new([].freeze)
13
+ raw.each_line do |line|
14
+ case line
15
+ when /\A(.+?):\s+(.+)\z/om
16
+ name, value = $1, $2
17
+ name = name.tr('-', '_').to_sym
18
+ value.strip!
19
+
20
+ header[name] = [] unless header.has_key?(name)
21
+ header[name] << value
22
+ end
23
+ end
24
+ header
15
25
  end
16
26
 
17
- # Normalize all URIs before they are used for anything else
18
- def HTTPUtil.normalize(uri_str, scheme = true)
19
-
20
-
21
- #STDERR.puts " BEFORE: " << uri_str
22
- if (!uri_str.index(/^https?:\/\//))
23
- uri_str = 'http://' << uri_str
24
- end
25
- #STDERR.puts " AFTER: " << uri_str
27
+ # Takes a string or array. See parse_header_body for further info.
28
+ # Returns +method+, +http_version+, +uri+, +header+, +body+
29
+ def HTTPUtil.parse_http_request(data, host=:unknown, port=80)
30
+ return unless data && !data.empty?
31
+ data = data.split(/\r?\n/) unless data.kind_of? Array
32
+ data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
33
+ request_line = data.shift # i.e. GET /path HTTP/1.1
34
+ method, path, http_version = nil
26
35
 
27
- uri_str.gsub!(/\s/, '%20')
28
-
29
- uri = URI.parse(uri_str)
30
-
31
- uri_clean = ""
32
-
33
- # TODO: use URI.to_s instead of manually creating the string
34
-
35
- if (scheme)
36
- uri_clean << uri.scheme.to_s + '://'
36
+ # With WEBrick and other proxies, the entire URI is included in HTTP requests.
37
+ # i.e. GET http://stellaaahhhh.com/streetcar.png HTTP/1.1
38
+ # The parser is expecting just the absolute path.
39
+ if request_line =~ /^(\S+)\s+(http:\/\/.+)\s+(HTTP.+)?/mo
40
+ uri = URI.parse($2)
41
+ request_line = "#{$1} #{uri.request_uri} #{$3}"
42
+ host = uri.host
37
43
  end
38
44
 
45
+ if request_line =~ /^(\S+)\s+(\S+)(?:\s+HTTP\/(\d+\.\d+))?/mo
46
+ method = $1
47
+ http_version = $3 # Comes before $2 b/c the split resets the numbered vars
48
+ path, query_string = $2.split('?')
39
49
 
40
- if (!uri.userinfo.nil?)
41
- uri_clean << uri.userinfo.to_s
42
- uri_clean << '@'
43
- end
44
-
45
- #uri.host.gsub!(/^www\./, '')
46
-
47
- uri_clean << uri.host.to_s
48
-
49
- if (!uri.port.nil? && uri.port != 80 && uri.port != 443)
50
- uri_clean << ':' + uri.port.to_s
51
- end
52
-
53
-
54
-
55
- if (!uri.path.nil? && !uri.path.empty?)
56
- uri_clean << uri.path
57
- elsif
58
- uri_clean << '/'
59
- end
60
-
61
-
62
- if (!uri.query.nil? && !uri.path.empty?)
63
- uri_clean << "?" << uri.query
50
+ # We only process the header and body data when we know we're
51
+ # starting from the beginning of a request string. We don't
52
+ # want no partials.
53
+ header, body = HTTPUtil.parse_header_body(data)
54
+ query = HTTPUtil.parse_query(method, query_string)
55
+
56
+
57
+ # TODO: Parse username/password
58
+ uri = URI::HTTP.build({
59
+ :scheme => 'http',
60
+ :host => header[:Host][0] || host.to_s,
61
+ :port => port,
62
+ :path => path,
63
+ :query => query_string
64
+ })
65
+
66
+ else
67
+ rl = request_line.sub(/\x0d?\x0a\z/o, '')
68
+ raise "Bad Request-Line `#{rl}'."
64
69
  end
65
70
 
66
- #STDERR.puts "IN: " << uri_str
67
- #STDERR.puts "OUT: " << uri_clean
68
-
69
- uri_clean
71
+ return method, http_version, uri, header, body
70
72
  end
71
73
 
72
- def HTTPUtil.fetch_content(uri, limit = 10)
73
- res = self.fetch(uri,limit)
74
- return (res) ? res.body : ""
75
- end
76
74
 
77
- def HTTPUtil.fetch(uri, limit = 10)
78
-
79
- # You should choose better exception.
80
- raise ArgumentError, 'HTTP redirect too deep' if limit == 0
81
- STDERR.puts "URL: #{uri.to_s}"
82
- uri = URI.parse(uri) if uri.is_a? String
75
+ # Takes a string or array. See parse_header_body for further info.
76
+ # Returns +status+, +http_version+, +message+, +header+, +body+
77
+ def HTTPUtil.parse_http_response(data=[])
78
+ return unless data && !data.empty?
79
+ data = data.split(/\r?\n/) unless data.kind_of? Array
80
+ data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
81
+ status_line = data.shift # ie. HTTP/1.1 200 OK
82
+ http_version, status, message = nil
83
83
 
84
- begin
85
- timeout(@@timeout) do
86
- response = Net::HTTP.get_response(uri)
84
+ if status_line =~ /^HTTP\/(\d.+?)(\s+(\d\d\d)\s+(.+))?$/mo
85
+ http_version = $1
86
+ status = $2
87
+ message = $4
87
88
 
89
+ header, body, query = HTTPUtil.parse_header_body(data)
88
90
 
89
- case response
90
- when Net::HTTPSuccess then response
91
- when Net::HTTPRedirection then fetch(response['location'], limit - 1)
92
- else
93
- STDERR.puts "Not found: " << uri.to_s
94
- end
95
- end
96
- rescue TimeoutError
97
- STDERR.puts "Net::HTTP timed out for " << uri.to_s
98
- return
99
- rescue => ex
100
- STDERR.puts "Error: #{ex.message}"
91
+ else
92
+ raise "Bad Response-Line `#{status_line}'."
101
93
  end
102
-
94
+
95
+ return status, http_version, message, header, body
103
96
  end
104
97
 
105
- def HTTPUtil.post(uri, params = {}, limit = 10)
106
-
107
- # You should choose better exception.
108
- raise ArgumentError, 'HTTP redirect too deep' if limit == 0
98
+ # Process everything after the first line of an HTTP request or response:
99
+ # GET / HTTP/1.1
100
+ # HTTP/1.1 200 OK
101
+ # etc...
102
+ # Used by parse_http_request and parse_http_response but can be used separately.
103
+ # Takes a string or array of strings. A string should be formatted like an HTTP
104
+ # request or response. If a body is present it should be separated by two newlines.
105
+ # An array of string should contain an empty or nil element between the header
106
+ # and body content. This will happen naturally if the raw lines were split by
107
+ # a single line terminator. (i.e. /\n/ rather than /\n\n/)
108
+ # Returns header (hash), body (string)
109
+ def HTTPUtil.parse_header_body(data=[])
110
+ header, body = {}, nil
111
+ data = data.split(/\r?\n/) unless data.kind_of? Array
112
+ data.shift while (data[0].nil? || data[0].empty?) # Remove leading empties
109
113
 
110
- uri = URI.parse(uri) if uri.is_a? String
114
+ return header, body unless data && !data.empty?
111
115
 
112
- begin
113
- timeout(@@timeout) do
114
- response = Net::HTTP.post_form(uri, params)
115
-
116
- case response
117
- when Net::HTTPSuccess then response
118
- when Net::HTTPRedirection then fetch(response['location'], limit - 1)
119
- else
120
- STDERR.puts "Error for " << uri.to_s
121
- STDERR.puts response.body
122
- end
123
- end
124
- rescue TimeoutError
125
- STDERR.puts "Net::HTTP timed out for " << uri.to_s
126
- return
127
- end
116
+ #puts data.to_yaml
128
117
 
118
+ # Skip that first line if it exists
119
+ data.shift if data[0].match(/\AHTTP|GET|POST|DELETE|PUT|HEAD/mo)
129
120
 
121
+ header_lines = []
122
+ header_lines << data.shift while (!data[0].nil? && !data[0].empty?)
123
+ header = HTTPUtil::parse_header(header_lines.join($/))
130
124
 
125
+ # We omit the blank line that delimits the header from the body
126
+ body = data[1..-1].join($/) unless data.empty?
127
+
128
+ return header, body
131
129
  end
132
130
 
133
- def HTTPUtil.exists(uri)
134
-
135
- begin
136
- response = fetch(uri)
137
- case response
138
- when Net::HTTPSuccess then true
139
- when Net::HTTPRedirection then fetch(response['location'], limit - 1)
131
+ def HTTPUtil.parse_query(request_method, query_string, content_type='', body='')
132
+ query = Hash.new([].freeze)
133
+
134
+ if request_method == "GET" || request_method == "HEAD"
135
+ query = HTTPUtil::parse_query_from_string(query_string)
136
+ elsif content_type =~ /^application\/x-www-form-urlencoded/
137
+ query = HTTPUtil::parse_query_from_string(body)
138
+ elsif content_type =~ /^multipart\/form-data; boundary=(.+)/
139
+ boundary = $1.tr('"', '')
140
+ query = HTTPUtil::parse_form_data(body, boundary)
140
141
  else
141
- false
142
+ query
142
143
  end
143
-
144
- rescue Exception => e
145
- STDERR.puts "Problem: " + e.message
146
- false
147
- end
148
-
144
+
145
+ query
149
146
  end
150
147
 
151
- def HTTPUtil.parse_query(query)
152
- params = Hash.new([].freeze)
153
-
154
- query.split(/[&;]/n).each do |pairs|
155
- key, value = pairs.split('=',2).collect{|v| URI.unescape(v) }
156
- if params.has_key?(key)
157
- params[key].push(value)
148
+ def HTTPUtil.validate_method(meth='GET')
149
+ (VALID_METHODS.member? meth.upcase) ? meth : VALID_METHODS[0]
150
+ end
151
+
152
+ # Parses a query string by breaking it up at the '&'
153
+ # and ';' characters. You can also use this to parse
154
+ # cookies by changing the characters used in the second
155
+ # parameter (which defaults to '&;'.
156
+ # Stolen from Mongrel
157
+ def HTTPUtil.parse_query_from_string(qs, d = '&;')
158
+ params = {}
159
+ (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
160
+ k, v=unescape(p).split('=',2)
161
+ next unless k
162
+ k = k.tr('-', '_').to_sym
163
+ if cur = params[k]
164
+ if cur.class == Array
165
+ params[k] << v
166
+ else
167
+ params[k] = [cur, v]
168
+ end
158
169
  else
159
- params[key] = [value]
170
+ params[k] = v
160
171
  end
161
- end
172
+ }
162
173
 
163
- params
174
+ return params
164
175
  end
176
+
165
177
 
166
- def HTTPUtil.validate_method(meth='GET')
167
- meth = (VALID_METHODS.member? meth.upcase) ? meth : VALID_METHODS[0]
178
+
179
+ # Based on WEBrick::HTTPutils::parse_form_data
180
+ def HTTPUtil.parse_form_data(io, boundary)
181
+ boundary_regexp = /\A--#{boundary}(--)?#{$/}\z/
182
+ form_data = Hash.new
183
+ return form_data unless io
184
+ data = nil
185
+ io.each_line{|line|
186
+ if boundary_regexp =~ line
187
+ if data
188
+ data.chop!
189
+ key = data.name.tr('-', '_').to_sym
190
+ if form_data.has_key?(key)
191
+ form_data[key].append_data(data)
192
+ else
193
+ form_data[key] = data
194
+ end
195
+ end
196
+ data = FormData.new
197
+ next
198
+ else
199
+ if data
200
+ data << line
201
+ end
202
+ end
203
+ }
204
+ return form_data
168
205
  end
169
206
 
207
+
170
208
  # Extend the basic query string parser provided by the cgi module.
171
209
  # converts single valued params (the most common case) to
172
210
  # objects instead of arrays
@@ -178,7 +216,6 @@ require 'timeout'
178
216
  # hash of parameters, contains arrays for multivalued parameters
179
217
  # (multiselect, checkboxes , etc)
180
218
  # If no query string is provided (nil or "") returns an empty hash.
181
-
182
219
  def HTTPUtil.query_to_hash(query_string)
183
220
  return {} unless query_string
184
221
 
@@ -205,6 +242,25 @@ require 'timeout'
205
242
  #return pairs.join(";")
206
243
  end
207
244
 
245
+
246
+
247
+ # Performs URI escaping so that you can construct proper
248
+ # query strings faster. Use this rather than the cgi.rb
249
+ # version since it's faster. (Stolen from Mongrel/Camping).
250
+ def HTTPUtil.escape(s)
251
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
252
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
253
+ }.tr(' ', '+')
254
+ end
255
+
256
+
257
+ # Unescapes a URI escaped string. (Stolen from Mongrel/Camping).
258
+ def HTTPUtil.unescape(s)
259
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
260
+ [$1.delete('%')].pack('H*')
261
+ }
262
+ end
263
+
208
264
 
209
265
  end
210
266
 
@@ -6,6 +6,7 @@ module MathUtil
6
6
  n = 0
7
7
  mean = 0.0
8
8
  s = 0.0
9
+
9
10
  population.each { |x|
10
11
  n = n + 1
11
12
  delta = (x - mean).to_f
@@ -13,7 +14,9 @@ module MathUtil
13
14
  s = (s + delta * (x - mean)).to_f
14
15
  }
15
16
 
16
- return s / n
17
+ s / n
18
+ rescue => ex
19
+ 0.0
17
20
  end
18
21
 
19
22
  # calculate the standard deviation of a population
@@ -43,7 +46,8 @@ module Enumerable
43
46
  # Sum of all the elements of the Enumerable
44
47
 
45
48
  def sum
46
- return self.inject(0) { |acc, i| acc + i }
49
+ return 0 if !self || self.empty?
50
+ self.inject(0) { |acc, i| acc.to_f + i.to_f }
47
51
  end
48
52
 
49
53
  ##
@@ -52,7 +56,10 @@ module Enumerable
52
56
  # The Enumerable must respond to #length
53
57
 
54
58
  def average
55
- return self.sum / self.length.to_f
59
+ return 0 unless self
60
+ self.sum / self.length.to_f
61
+ rescue => ex
62
+ 0.0
56
63
  end
57
64
 
58
65
  ##
@@ -61,9 +68,12 @@ module Enumerable
61
68
  # The Enumerable must respond to #length
62
69
 
63
70
  def sample_variance
64
- avg = self.average
65
- sum = self.inject(0) { |acc, i| acc + (i - avg) ** 2 }
66
- return (1 / self.length.to_f * sum)
71
+ return 0 unless self
72
+ avg = self.average
73
+ sum = self.sum
74
+ (1 / self.length.to_f * sum)
75
+ rescue => ex
76
+ 0.0
67
77
  end
68
78
 
69
79
  ##
@@ -72,7 +82,10 @@ module Enumerable
72
82
  # The Enumerable must respond to #length
73
83
 
74
84
  def standard_deviation
75
- return Math.sqrt(self.sample_variance)
85
+ return 0 unless self
86
+ Math.sqrt(self.sample_variance)
87
+ rescue => ex
88
+ 0.0
76
89
  end
77
90
 
78
91
  end