stella 0.5.3 → 0.5.4

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.
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