mouth 0.8.0
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.
- data/.gitignore +7 -0
- data/Capfile +26 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +138 -0
- data/Rakefile +19 -0
- data/TODO +32 -0
- data/bin/mouth +77 -0
- data/bin/mouth-console +18 -0
- data/bin/mouth-endoscope +18 -0
- data/lib/mouth.rb +61 -0
- data/lib/mouth/dashboard.rb +25 -0
- data/lib/mouth/endoscope.rb +120 -0
- data/lib/mouth/endoscope/public/222222_256x240_icons_icons.png +0 -0
- data/lib/mouth/endoscope/public/application.css +464 -0
- data/lib/mouth/endoscope/public/application.js +938 -0
- data/lib/mouth/endoscope/public/backbone.js +1158 -0
- data/lib/mouth/endoscope/public/d3.js +4707 -0
- data/lib/mouth/endoscope/public/d3.time.js +687 -0
- data/lib/mouth/endoscope/public/jquery-ui-1.8.16.custom.min.js +177 -0
- data/lib/mouth/endoscope/public/jquery.js +4 -0
- data/lib/mouth/endoscope/public/json2.js +480 -0
- data/lib/mouth/endoscope/public/keymaster.js +163 -0
- data/lib/mouth/endoscope/public/linen.js +46 -0
- data/lib/mouth/endoscope/public/seven.css +68 -0
- data/lib/mouth/endoscope/public/seven.js +291 -0
- data/lib/mouth/endoscope/public/underscore.js +931 -0
- data/lib/mouth/endoscope/views/dashboard.erb +67 -0
- data/lib/mouth/graph.rb +58 -0
- data/lib/mouth/instrument.rb +56 -0
- data/lib/mouth/record.rb +72 -0
- data/lib/mouth/runner.rb +89 -0
- data/lib/mouth/sequence.rb +284 -0
- data/lib/mouth/source.rb +76 -0
- data/lib/mouth/sucker.rb +235 -0
- data/lib/mouth/version.rb +3 -0
- data/mouth.gemspec +28 -0
- data/test/sequence_test.rb +163 -0
- data/test/sucker_test.rb +55 -0
- data/test/test_helper.rb +5 -0
- metadata +167 -0
data/lib/mouth/source.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module Mouth
|
2
|
+
class Source
|
3
|
+
|
4
|
+
attr_accessor :all_attributes
|
5
|
+
|
6
|
+
def initialize(tuple)
|
7
|
+
self.all_attributes = tuple
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns an array of tuples:
|
11
|
+
# [{source: "auth.inline_logged_in", kind: "counter|timer"}, ...]
|
12
|
+
def self.all
|
13
|
+
col_names = Mouth.mongo.collections.collect(&:name) - %w(dashboards graphs system.indexes)
|
14
|
+
col_names.select! {|c| c =~ /^mouth_.+/ }
|
15
|
+
|
16
|
+
tuples = []
|
17
|
+
col_names.each do |col_name|
|
18
|
+
namespace = col_name.match(/mouth_(.+)/)[1]
|
19
|
+
counters, timers = all_for_collection(Mouth.collection(col_name))
|
20
|
+
counters.each {|s| tuples << {:source => "#{namespace}.#{s}", :kind => "counter"} }
|
21
|
+
timers.each {|s| tuples << {:source => "#{namespace}.#{s}", :kind => "timer"} }
|
22
|
+
end
|
23
|
+
|
24
|
+
tuples.sort_by {|t| "#{t[:kind]}#{t[:source]}" }.collect {|t| new(t) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.all_for_collection(col, window = Time.now.to_i / 60 - 120)
|
28
|
+
map_function = <<-JS
|
29
|
+
function() {
|
30
|
+
var k, vh;
|
31
|
+
for (k in this.c) {
|
32
|
+
vh = {};
|
33
|
+
vh[k] = true;
|
34
|
+
emit("counters", {ks: vh});
|
35
|
+
}
|
36
|
+
for (k in this.m) {
|
37
|
+
vh = {};
|
38
|
+
vh[k] = true;
|
39
|
+
emit("timers", {ks: vh});
|
40
|
+
}
|
41
|
+
}
|
42
|
+
JS
|
43
|
+
|
44
|
+
reduce_function = <<-JS
|
45
|
+
function(key, values) {
|
46
|
+
var ret = {ks: {}}, k;
|
47
|
+
|
48
|
+
values.forEach(function(value) {
|
49
|
+
for (k in value.ks) {
|
50
|
+
ret.ks[k] = true
|
51
|
+
}
|
52
|
+
});
|
53
|
+
return ret;
|
54
|
+
}
|
55
|
+
JS
|
56
|
+
|
57
|
+
result = col.map_reduce(map_function, reduce_function, :out => {:inline => true}, :raw => true, :query => {"t" => {"$gte" => window}})
|
58
|
+
|
59
|
+
counters = []
|
60
|
+
timers = []
|
61
|
+
if result && result["results"].is_a?(Array)
|
62
|
+
result["results"].each do |r|
|
63
|
+
k, v = r["_id"], r["value"]
|
64
|
+
if k == "timers"
|
65
|
+
timers << v["ks"].keys
|
66
|
+
elsif k == "counters"
|
67
|
+
counters << v["ks"].keys
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
[counters.flatten.compact.uniq, timers.flatten.compact.uniq]
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/lib/mouth/sucker.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'em-mongo'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
module Mouth
|
5
|
+
|
6
|
+
class SuckerConnection < EM::Connection
|
7
|
+
attr_accessor :sucker
|
8
|
+
|
9
|
+
def receive_data(data)
|
10
|
+
Mouth.logger.debug "UDP packet: '#{data}'"
|
11
|
+
|
12
|
+
sucker.store!(data)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Sucker
|
17
|
+
|
18
|
+
# Host/Port to suck UDP packets on
|
19
|
+
attr_accessor :host
|
20
|
+
attr_accessor :port
|
21
|
+
|
22
|
+
# Actual EM::Mongo connection
|
23
|
+
attr_accessor :mongo_db
|
24
|
+
|
25
|
+
# Info to connect to mongo
|
26
|
+
attr_accessor :mongo_db_name
|
27
|
+
attr_accessor :mongo_hosts
|
28
|
+
|
29
|
+
# Accumulators of our data
|
30
|
+
attr_accessor :counters
|
31
|
+
attr_accessor :timers
|
32
|
+
|
33
|
+
# Stats
|
34
|
+
attr_accessor :udp_packets_received
|
35
|
+
attr_accessor :mongo_flushes
|
36
|
+
|
37
|
+
def initialize(options = {})
|
38
|
+
self.host = options[:host] || "localhost"
|
39
|
+
self.port = options[:port] || 8889
|
40
|
+
self.mongo_db_name = options[:mongo_db_name] || "mouth"
|
41
|
+
self.mongo_hosts = options[:mongo_hosts] || ["localhost"]
|
42
|
+
|
43
|
+
self.udp_packets_received = 0
|
44
|
+
self.mongo_flushes = 0
|
45
|
+
|
46
|
+
self.counters = {}
|
47
|
+
self.timers = {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def suck!
|
51
|
+
EM.run do
|
52
|
+
# Connect to mongo now
|
53
|
+
self.mongo_db
|
54
|
+
|
55
|
+
EM.open_datagram_socket host, port, SuckerConnection do |conn|
|
56
|
+
conn.sucker = self
|
57
|
+
end
|
58
|
+
|
59
|
+
EM.add_periodic_timer(10) do
|
60
|
+
Mouth.logger.info "Counters: #{self.counters.inspect}"
|
61
|
+
Mouth.logger.info "Timers: #{self.timers.inspect}"
|
62
|
+
self.flush!
|
63
|
+
self.set_procline!
|
64
|
+
end
|
65
|
+
|
66
|
+
EM.next_tick do
|
67
|
+
Mouth.logger.info "Mouth reactor started..."
|
68
|
+
self.set_procline!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# counter: gorets:1|c
|
74
|
+
# counter w/ sampling: gorets:1|c|@0.1
|
75
|
+
# timer: glork:320|ms
|
76
|
+
# (future) gauge: gaugor:333|g
|
77
|
+
def store!(data)
|
78
|
+
key_value, command_sampling = data.split("|", 2)
|
79
|
+
key, value = key_value.to_s.split(":")
|
80
|
+
command, sampling = command_sampling.split("|")
|
81
|
+
|
82
|
+
key = Mouth.parse_key(key).join(".")
|
83
|
+
value = value.to_f
|
84
|
+
|
85
|
+
ts = minute_timestamps
|
86
|
+
|
87
|
+
if command == "ms"
|
88
|
+
self.timers[ts] ||= {}
|
89
|
+
self.timers[ts][key] ||= []
|
90
|
+
self.timers[ts][key] << value
|
91
|
+
elsif command == "c"
|
92
|
+
factor = 1.0
|
93
|
+
if sampling
|
94
|
+
factor = sampling.sub("@", "").to_f
|
95
|
+
factor = (factor == 0.0 || factor > 1.0) ? 1.0 : 1.0 / factor
|
96
|
+
end
|
97
|
+
self.counters[ts] ||= {}
|
98
|
+
self.counters[ts][key] ||= 0.0
|
99
|
+
self.counters[ts][key] += value * factor
|
100
|
+
end
|
101
|
+
|
102
|
+
self.udp_packets_received += 1
|
103
|
+
end
|
104
|
+
|
105
|
+
def flush!
|
106
|
+
ts = minute_timestamps
|
107
|
+
limit_ts = ts - 1
|
108
|
+
mongo_docs = {}
|
109
|
+
|
110
|
+
# We're going to construct mongo_docs which look like this:
|
111
|
+
# "mycollections:234234": { # NOTE: this timpstamp will be popped into .t = 234234
|
112
|
+
# c: {
|
113
|
+
# happenings: 37,
|
114
|
+
# affairs: 3
|
115
|
+
# },
|
116
|
+
# m: {
|
117
|
+
# occasions: {...}
|
118
|
+
# }
|
119
|
+
# }
|
120
|
+
|
121
|
+
self.counters.each do |cur_ts, counters_to_save|
|
122
|
+
if cur_ts <= limit_ts
|
123
|
+
counters_to_save.each do |counter_key, value|
|
124
|
+
ns, sub_key = Mouth.parse_key(counter_key)
|
125
|
+
mongo_key = "#{ns}:#{ts}"
|
126
|
+
mongo_docs[mongo_key] ||= {}
|
127
|
+
|
128
|
+
cur_mongo_doc = mongo_docs[mongo_key]
|
129
|
+
cur_mongo_doc["c"] ||= {}
|
130
|
+
cur_mongo_doc["c"][sub_key] = value
|
131
|
+
end
|
132
|
+
|
133
|
+
self.counters.delete(cur_ts)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
self.timers.each do |cur_ts, timers_to_save|
|
138
|
+
if cur_ts <= limit_ts
|
139
|
+
timers_to_save.each do |timer_key, values|
|
140
|
+
ns, sub_key = Mouth.parse_key(timer_key)
|
141
|
+
mongo_key = "#{ns}:#{ts}"
|
142
|
+
mongo_docs[mongo_key] ||= {}
|
143
|
+
|
144
|
+
cur_mongo_doc = mongo_docs[mongo_key]
|
145
|
+
cur_mongo_doc["m"] ||= {}
|
146
|
+
cur_mongo_doc["m"][sub_key] = analyze_timer(values)
|
147
|
+
end
|
148
|
+
|
149
|
+
self.timers.delete(cur_ts)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
save_documents!(mongo_docs)
|
154
|
+
end
|
155
|
+
|
156
|
+
def save_documents!(mongo_docs)
|
157
|
+
Mouth.logger.info "Saving Docs: #{mongo_docs.inspect}"
|
158
|
+
|
159
|
+
mongo_docs.each do |key, doc|
|
160
|
+
ns, ts = key.split(":")
|
161
|
+
collection_name = "mouth_#{ns}"
|
162
|
+
doc["t"] = ts.to_i
|
163
|
+
|
164
|
+
self.mongo_db.collection(collection_name).insert(doc)
|
165
|
+
end
|
166
|
+
|
167
|
+
self.mongo_flushes += 1 if mongo_docs.any?
|
168
|
+
end
|
169
|
+
|
170
|
+
def mongo_db
|
171
|
+
@mongo_db ||= begin
|
172
|
+
if self.mongo_hosts.length == 1
|
173
|
+
EM::Mongo::Connection.new(self.mongo_hosts.first).db(self.mongo_db_name)
|
174
|
+
else
|
175
|
+
raise "TODO: ability to connect to a replica set."
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def set_procline!
|
181
|
+
$0 = "mouth [started] [UDP Recv: #{self.udp_packets_received}] [Mongo saves: #{self.mongo_flushes}]"
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def minute_timestamps
|
187
|
+
Time.now.to_i / 60
|
188
|
+
end
|
189
|
+
|
190
|
+
def analyze_timer(values)
|
191
|
+
values.sort!
|
192
|
+
|
193
|
+
count = values.length
|
194
|
+
min = values[0]
|
195
|
+
max = values[-1]
|
196
|
+
mean = nil
|
197
|
+
sum = 0.0
|
198
|
+
median = median_for(values)
|
199
|
+
stddev = 0.0
|
200
|
+
|
201
|
+
values.each {|v| sum += v }
|
202
|
+
mean = sum / count
|
203
|
+
|
204
|
+
values.each do |v|
|
205
|
+
devi = v - mean
|
206
|
+
stddev += (devi * devi)
|
207
|
+
end
|
208
|
+
|
209
|
+
stddev = Math.sqrt(stddev / count)
|
210
|
+
|
211
|
+
{
|
212
|
+
"count" => count,
|
213
|
+
"min" => min,
|
214
|
+
"max" => max,
|
215
|
+
"mean" => mean,
|
216
|
+
"sum" => sum,
|
217
|
+
"median" => median,
|
218
|
+
"stddev" => stddev,
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
def median_for(values)
|
223
|
+
count = values.length
|
224
|
+
middle = count / 2
|
225
|
+
if count == 0
|
226
|
+
return 0
|
227
|
+
elsif count % 2 == 0
|
228
|
+
return (values[middle] + values[middle - 1]).to_f / 2
|
229
|
+
else
|
230
|
+
return values[middle]
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
end # class Sucker
|
235
|
+
end # module
|
data/mouth.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require './lib/mouth/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'mouth'
|
5
|
+
s.version = Mouth::VERSION
|
6
|
+
s.author = 'Jonathan Novak'
|
7
|
+
s.email = 'jnovak@gmail.com'
|
8
|
+
s.homepage = 'http://github.com/cypriss/mouth'
|
9
|
+
s.summary = 'Collect and view stats in real time.'
|
10
|
+
s.description = 'Ruby Daemon collects statistics via UDP packets, stores them in Mongo, and then views them via a user friendly Sintra app.'
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.test_files = `git ls-files test`.split("\n")
|
14
|
+
s.require_path = 'lib'
|
15
|
+
s.bindir = 'bin'
|
16
|
+
s.executables << 'mouth' << 'mouth-endoscope'
|
17
|
+
|
18
|
+
s.add_runtime_dependency 'em-mongo', '~> 0.4.2' # For the EM collector
|
19
|
+
s.add_runtime_dependency 'mongo', '~> 1.6' # For the sinatra app
|
20
|
+
s.add_runtime_dependency 'bson_ext', '~> 1.6'
|
21
|
+
s.add_runtime_dependency 'eventmachine', '~> 0.12.10'
|
22
|
+
s.add_runtime_dependency 'vegas', '~> 0.1.8'
|
23
|
+
s.add_runtime_dependency 'sinatra', '~> 1.3.1'
|
24
|
+
s.add_runtime_dependency 'yajl-ruby', '~> 1.1.0'
|
25
|
+
|
26
|
+
s.required_ruby_version = '>= 1.9.2'
|
27
|
+
s.required_rubygems_version = '>= 1.3.4'
|
28
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
$LOAD_PATH.unshift 'test'
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
require 'mouth/sequence'
|
5
|
+
|
6
|
+
class SequenceTest < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
@start_time = Time.new(2012, 4, 1, 9, 30, 0, "-07:00")
|
9
|
+
@end_time = Time.new(2012, 4, 1, 11, 30, 0, "-07:00")
|
10
|
+
@namespace, @metric = Mouth.parse_key("test.test")
|
11
|
+
|
12
|
+
@start_timestamp = @start_time.to_i / 60
|
13
|
+
@end_timestamp = @end_time.to_i / 60
|
14
|
+
|
15
|
+
counter = 1
|
16
|
+
timer = {"count" => 5, "min" => 1, "max" => 10, "mean" => 5, "sum" => 20, "median" => 4, "stddev" => 2.3}
|
17
|
+
(@start_timestamp..@end_timestamp).each do |t|
|
18
|
+
# Insert the document into mongo
|
19
|
+
Mouth.collection(Mouth.mongo_collection_name(@namespace)).update({"t" => t}, {"$set" => {"c.#{@metric}" => counter, "m.#{@metric}" => timer}}, :upsert => true)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_minute_counter_sequences
|
24
|
+
seq = Mouth::Sequence.new("#{@namespace}.#{@metric}", :start_time => @start_time, :end_time => @end_time)
|
25
|
+
|
26
|
+
sequences = seq.sequences
|
27
|
+
|
28
|
+
assert_equal ["test"], sequences.keys
|
29
|
+
assert_equal 1, sequences.values.length
|
30
|
+
|
31
|
+
values = sequences.values.first
|
32
|
+
|
33
|
+
assert_equal 121, values.length
|
34
|
+
|
35
|
+
assert values.all? {|v| v == 1} # The 'test' sequence is all ones
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_15_minute_counter_sequences
|
39
|
+
seq = Mouth::Sequence.new("#{@namespace}.#{@metric}", :granularity_in_minutes => 15, :start_time => @start_time, :end_time => @end_time)
|
40
|
+
|
41
|
+
sequences = seq.sequences
|
42
|
+
|
43
|
+
assert_equal ["test"], sequences.keys
|
44
|
+
assert_equal 1, sequences.values.length
|
45
|
+
|
46
|
+
values = sequences.values.first
|
47
|
+
|
48
|
+
assert_equal 9, values.length
|
49
|
+
assert_equal [15.0, 15.0, 15.0, 15.0, 15.0, 15.0, 15.0, 15.0, 1.0], values
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_15_minute_timer_sequences_basic
|
53
|
+
seq = Mouth::Sequence.new("#{@namespace}.#{@metric}", :kind => :timer, :granularity_in_minutes => 15, :start_time => @start_time, :end_time => @end_time)
|
54
|
+
|
55
|
+
sequences = seq.sequences
|
56
|
+
assert_equal ["test"], sequences.keys
|
57
|
+
assert_equal 1, sequences.values.length
|
58
|
+
|
59
|
+
values = sequences.values.first
|
60
|
+
|
61
|
+
assert_equal 9, values.length
|
62
|
+
|
63
|
+
timer = {"count"=>75,
|
64
|
+
"min"=>1,
|
65
|
+
"max"=>10,
|
66
|
+
"mean"=>4,
|
67
|
+
"sum"=>300,
|
68
|
+
"median"=>4,
|
69
|
+
"stddev"=>3.780211634287159}
|
70
|
+
assert_equal timer, values.first
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_15_minute_timer_sequences_one_grouping
|
74
|
+
@start_time = Time.new(2012, 4, 1, 9, 30, 0, "-07:00")
|
75
|
+
@end_time = Time.new(2012, 4, 1, 9, 44, 0, "-07:00")
|
76
|
+
|
77
|
+
@start_timestamp = @start_time.to_i / 60
|
78
|
+
@end_timestamp = @end_time.to_i / 60
|
79
|
+
|
80
|
+
col = Mouth.collection(Mouth.mongo_collection_name("test"))
|
81
|
+
col.remove
|
82
|
+
|
83
|
+
# NOTE: this data is totally fake, maybe we can get some real data
|
84
|
+
timers = [
|
85
|
+
{"count"=>3, "min"=>15, "max"=>30, "mean"=>100, "sum"=>300, "median"=>4, "stddev"=>1},
|
86
|
+
{"count"=>6, "min"=>14, "max"=>32, "mean"=>90, "sum"=>540, "median"=>5, "stddev"=>2},
|
87
|
+
{"count"=>9, "min"=>13, "max"=>30, "mean"=>85, "sum"=>765, "median"=>6, "stddev"=>3},
|
88
|
+
{"count"=>12, "min"=>12, "max"=>30, "mean"=>80, "sum"=>960, "median"=>7, "stddev"=>4},
|
89
|
+
{"count"=>15, "min"=>11, "max"=>30, "mean"=>70, "sum"=>1050, "median"=>8, "stddev"=>5},
|
90
|
+
{"count"=>18, "min"=>10, "max"=>30, "mean"=>65, "sum"=>1170, "median"=>9, "stddev"=>6},
|
91
|
+
{"count"=>21, "min"=>9, "max"=>30, "mean"=>60, "sum"=>1260, "median"=>10, "stddev"=>7},
|
92
|
+
{"count"=>24, "min"=>8, "max"=>30, "mean"=>55, "sum"=>1320, "median"=>11, "stddev"=>8},
|
93
|
+
{"count"=>27, "min"=>7, "max"=>30, "mean"=>50, "sum"=>1350, "median"=>12, "stddev"=>9},
|
94
|
+
{"count"=>30, "min"=>6, "max"=>30, "mean"=>40, "sum"=>1200, "median"=>13, "stddev"=>10},
|
95
|
+
{"count"=>33, "min"=>16, "max"=>30, "mean"=>30, "sum"=>990, "median"=>14, "stddev"=>11},
|
96
|
+
{"count"=>36, "min"=>17, "max"=>30, "mean"=>20, "sum"=>720, "median"=>15, "stddev"=>12},
|
97
|
+
{"count"=>39, "min"=>18, "max"=>30, "mean"=>10, "sum"=>390, "median"=>16, "stddev"=>13},
|
98
|
+
{"count"=>42, "min"=>19, "max"=>30, "mean"=>5, "sum"=>210, "median"=>17, "stddev"=>14},
|
99
|
+
{"count"=>45, "min"=>20, "max"=>30, "mean"=>1, "sum"=>45, "median"=>18, "stddev"=>15}
|
100
|
+
]
|
101
|
+
i = 0
|
102
|
+
(@start_timestamp..@end_timestamp).each do |t|
|
103
|
+
col.update({"t" => t}, {"$set" => {"m.test" => timers[i]}}, :upsert => true)
|
104
|
+
i += 1
|
105
|
+
end
|
106
|
+
|
107
|
+
seq = Mouth::Sequence.new("test.test", :kind => :timer, :granularity_in_minutes => 15, :start_time => @start_time, :end_time => @end_time)
|
108
|
+
values = seq.sequence
|
109
|
+
|
110
|
+
assert_equal 1, values.length
|
111
|
+
|
112
|
+
value = values.first
|
113
|
+
|
114
|
+
expected_count = timers.collect {|t| t["count"] }.inject(0) {|s,c| s + c }
|
115
|
+
expected_sum = timers.collect {|t| t["sum"] }.inject(0) {|s,c| s + c }
|
116
|
+
|
117
|
+
assert_equal expected_count, value["count"]
|
118
|
+
assert_equal expected_sum, value["sum"]
|
119
|
+
assert_equal timers.collect {|t| t["min"] }.min, value["min"]
|
120
|
+
assert_equal timers.collect {|t| t["max"] }.max, value["max"]
|
121
|
+
assert_equal expected_sum / expected_count, value["mean"].to_i
|
122
|
+
assert_equal 11, value["median"]
|
123
|
+
assert_equal 29, value["stddev"].to_i # NOTE: i don't actually know if this is correct
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_empty_timer
|
128
|
+
@start_time = Time.new(2012, 4, 1, 9, 30, 0, "-07:00")
|
129
|
+
@end_time = Time.new(2012, 4, 1, 9, 44, 0, "-07:00")
|
130
|
+
|
131
|
+
@start_timestamp = @start_time.to_i / 60
|
132
|
+
@end_timestamp = @end_time.to_i / 60
|
133
|
+
|
134
|
+
col = Mouth.collection(Mouth.mongo_collection_name("test"))
|
135
|
+
col.remove
|
136
|
+
seq = Mouth::Sequence.new("test.test", :kind => :timer, :granularity_in_minutes => 15, :start_time => @start_time, :end_time => @end_time)
|
137
|
+
values = seq.sequence
|
138
|
+
|
139
|
+
assert_equal [{"count"=>0,
|
140
|
+
"min"=>nil,
|
141
|
+
"max"=>nil,
|
142
|
+
"mean"=>nil,
|
143
|
+
"sum"=>0,
|
144
|
+
"median"=>nil,
|
145
|
+
"stddev"=>nil}], values
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_empty_sequence
|
149
|
+
@start_time = Time.new(2012, 4, 1, 9, 30, 0, "-07:00")
|
150
|
+
@end_time = Time.new(2012, 4, 1, 9, 44, 0, "-07:00")
|
151
|
+
|
152
|
+
@start_timestamp = @start_time.to_i / 60
|
153
|
+
@end_timestamp = @end_time.to_i / 60
|
154
|
+
|
155
|
+
col = Mouth.collection(Mouth.mongo_collection_name("test"))
|
156
|
+
col.remove
|
157
|
+
seq = Mouth::Sequence.new("test.test", :kind => :counter, :granularity_in_minutes => 15, :start_time => @start_time, :end_time => @end_time)
|
158
|
+
values = seq.sequence
|
159
|
+
|
160
|
+
assert_equal [0], values
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|