simple_metrics 0.0.1 → 0.2.2
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/bin/populate +25 -0
- data/lib/simple_metrics/bucket.rb +74 -19
- data/lib/simple_metrics/data_point.rb +162 -0
- data/lib/simple_metrics/functions.rb +70 -0
- data/lib/simple_metrics/graph.rb +58 -0
- data/lib/simple_metrics/mongo.rb +2 -1
- data/lib/simple_metrics/server.rb +11 -11
- data/lib/simple_metrics/version.rb +1 -1
- data/lib/simple_metrics.rb +6 -2
- data/simple_metrics.gemspec +2 -2
- data/spec/bucket_spec.rb +114 -22
- data/spec/data_point_spec.rb +131 -0
- data/spec/graph_spec.rb +89 -0
- metadata +37 -25
- data/lib/simple_metrics/stats.rb +0 -99
- data/spec/stats_spec.rb +0 -61
data/bin/populate
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "bundler/setup"
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require "simple_metrics"
|
8
|
+
|
9
|
+
ts = Time.now.to_i
|
10
|
+
|
11
|
+
name = "test.post.clicks2"
|
12
|
+
|
13
|
+
SimpleMetrics::Mongo.ensure_collections_exist
|
14
|
+
|
15
|
+
SimpleMetrics::Bucket.all.each do |bucket|
|
16
|
+
ts_bucket = bucket.ts_bucket(ts)
|
17
|
+
|
18
|
+
previous_value = rand(300)
|
19
|
+
(1..100).each do |index|
|
20
|
+
ts = ts_bucket - (bucket.seconds*index)
|
21
|
+
value = previous_value+rand(20)
|
22
|
+
data_point = SimpleMetrics::DataPoint.create_counter(:name => name, :value => value)
|
23
|
+
bucket.save(data_point, ts)
|
24
|
+
end
|
25
|
+
end
|
@@ -21,14 +21,17 @@ module SimpleMetrics
|
|
21
21
|
Bucket.all.sort_by! { |r| r.seconds }[1..-1]
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
return if
|
26
|
-
SimpleMetrics.logger.info "#{Time.now} Flushing #{
|
24
|
+
def flush_data_points(data_points)
|
25
|
+
return if data_points.empty?
|
26
|
+
SimpleMetrics.logger.info "#{Time.now} Flushing #{data_points.count} counters to MongoDB"
|
27
27
|
|
28
28
|
ts = Time.now.utc.to_i
|
29
29
|
bucket = Bucket.first
|
30
|
-
|
31
|
-
|
30
|
+
data_points.group_by { |data| data.name }.each_pair do |name,dps|
|
31
|
+
data = DataPoint.aggregate(dps)
|
32
|
+
bucket.save(data, ts)
|
33
|
+
end
|
34
|
+
|
32
35
|
self.aggregate_all(ts)
|
33
36
|
end
|
34
37
|
|
@@ -41,10 +44,10 @@ module SimpleMetrics
|
|
41
44
|
SimpleMetrics.logger.debug "Aggregating #{bucket.name} #{previous_ts}....#{current_ts} (#{humanized_timestamp(previous_ts)}..#{humanized_timestamp(current_ts)})"
|
42
45
|
|
43
46
|
unless bucket.stats_exist_in_previous_ts?(previous_ts)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
bucket.save(
|
47
|
+
data_points = self.first.find_all_in_ts_range(previous_ts, current_ts)
|
48
|
+
data_points.group_by { |data| data.name }.each_pair do |name,dps|
|
49
|
+
data = DataPoint.aggregate(dps)
|
50
|
+
bucket.save(data, previous_ts)
|
48
51
|
end
|
49
52
|
end
|
50
53
|
end
|
@@ -88,33 +91,54 @@ module SimpleMetrics
|
|
88
91
|
|
89
92
|
def find(id)
|
90
93
|
mongo_result = mongo_coll.find_one({ :_id => id })
|
91
|
-
|
94
|
+
DataPoint.create_from_db(mongo_result)
|
92
95
|
end
|
93
96
|
|
94
97
|
def find_all_by_name(name)
|
95
|
-
mongo_result = mongo_coll.find({ :name => name })
|
96
|
-
mongo_result.inject([]) { |result, a| result <<
|
98
|
+
mongo_result = mongo_coll.find({ :name => name }).to_a
|
99
|
+
mongo_result.inject([]) { |result, a| result << DataPoint.create_from_db(a) }
|
97
100
|
end
|
98
101
|
|
99
102
|
def find_all_in_ts(ts)
|
100
|
-
mongo_result = mongo_coll.find({ :ts => ts_bucket(ts) })
|
101
|
-
mongo_result.inject([]) { |result, a| result <<
|
103
|
+
mongo_result = mongo_coll.find({ :ts => ts_bucket(ts) }).to_a
|
104
|
+
mongo_result.inject([]) { |result, a| result << DataPoint.create_from_db(a) }
|
102
105
|
end
|
103
106
|
|
104
107
|
def find_all_in_ts_by_name(ts, name)
|
105
|
-
mongo_result = mongo_coll.find({ :ts => ts_bucket(ts), :name => name })
|
106
|
-
mongo_result.inject([]) { |result, a| result <<
|
108
|
+
mongo_result = mongo_coll.find({ :ts => ts_bucket(ts), :name => name }).to_a
|
109
|
+
mongo_result.inject([]) { |result, a| result << DataPoint.create_from_db(a) }
|
107
110
|
end
|
108
111
|
|
109
|
-
def find_all_in_ts_range(
|
110
|
-
mongo_result = mongo_coll.find({ :ts => { "$gte" =>
|
111
|
-
mongo_result.inject([]) { |result, a| result <<
|
112
|
+
def find_all_in_ts_range(from, to)
|
113
|
+
mongo_result = mongo_coll.find({ :ts => { "$gte" => from, "$lte" => to }}).to_a
|
114
|
+
mongo_result.inject([]) { |result, a| result << DataPoint.create_from_db(a) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def find_all_in_ts_range_by_name(from, to, name)
|
118
|
+
mongo_result = mongo_coll.find({ :name => name, :ts => { "$gte" => from, "$lte" => to }}).to_a
|
119
|
+
mongo_result.inject([]) { |result, a| result << DataPoint.create_from_db(a) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def find_all_in_ts_range_by_wildcard(from, to, target)
|
123
|
+
target = target.gsub('.', '\.')
|
124
|
+
target = target.gsub('*', '.*')
|
125
|
+
mongo_result = mongo_coll.find({ :name => /#{target}/, :ts => { "$gte" => from, "$lte" => to } }).to_a
|
126
|
+
mongo_result.inject([]) { |result, a| result << DataPoint.create_from_db(a) }
|
127
|
+
end
|
128
|
+
|
129
|
+
def find_all_in_ts_range_by_regexp(from, to, target)
|
130
|
+
mongo_result = mongo_coll.find({ :name => /#{target}/, :ts => { "$gte" => from, "$lte" => to } }).to_a
|
131
|
+
mongo_result.inject([]) { |result, a| result << DataPoint.create_from_db(a) }
|
112
132
|
end
|
113
133
|
|
114
134
|
def stats_exist_in_previous_ts?(ts)
|
115
135
|
mongo_coll.find({ :ts => ts }).count > 0
|
116
136
|
end
|
117
137
|
|
138
|
+
def find_all_distinct_names
|
139
|
+
mongo_coll.distinct(:name).to_a
|
140
|
+
end
|
141
|
+
|
118
142
|
def save(stats, ts)
|
119
143
|
stats.ts = ts_bucket(ts)
|
120
144
|
result = mongo_coll.insert(stats.attributes)
|
@@ -129,5 +153,36 @@ module SimpleMetrics
|
|
129
153
|
@capped == true
|
130
154
|
end
|
131
155
|
|
156
|
+
def fill_gaps(from, to, query_result)
|
157
|
+
return query_result if query_result.nil? || query_result.size == 0
|
158
|
+
|
159
|
+
tmp_hash = DataPoint.ts_hash(query_result)
|
160
|
+
dp_template = query_result.first
|
161
|
+
|
162
|
+
result = []
|
163
|
+
each_ts(from, to) do |current_bucket_ts|
|
164
|
+
result <<
|
165
|
+
if tmp_hash.key?(current_bucket_ts)
|
166
|
+
tmp_hash[current_bucket_ts]
|
167
|
+
else
|
168
|
+
dp = dp_template.dup
|
169
|
+
dp.value = nil
|
170
|
+
dp.ts = current_bucket_ts
|
171
|
+
dp
|
172
|
+
end
|
173
|
+
end
|
174
|
+
result
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def each_ts(from, to)
|
180
|
+
current_bucket_ts = ts_bucket(from)
|
181
|
+
while (current_bucket_ts <= ts_bucket(to))
|
182
|
+
yield(current_bucket_ts)
|
183
|
+
current_bucket_ts = current_bucket_ts + seconds
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
132
187
|
end
|
133
188
|
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module SimpleMetrics
|
3
|
+
|
4
|
+
class DataPoint
|
5
|
+
|
6
|
+
class NonMatchingTypesError < Exception; end
|
7
|
+
class ParserError < Exception; end
|
8
|
+
|
9
|
+
# examples:
|
10
|
+
# com.example.test1:1|c
|
11
|
+
# com.example.test2:-1|c
|
12
|
+
# com.example.test2:50|g
|
13
|
+
# com.example.test3:5|c|@0.1
|
14
|
+
# com.example.test4:44|ms
|
15
|
+
REGEXP = /^([\d\w_.]*):(-?[\d]*)\|(c|g|ms){1}(\|@([.\d]+))?$/i
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
def parse(str)
|
20
|
+
if str =~ REGEXP
|
21
|
+
name, value, type, sample_rate = $1, $2, $3, $5
|
22
|
+
if type == "ms"
|
23
|
+
# TODO: implement sample_rate handling
|
24
|
+
create_timing(:name => name, :value => value)
|
25
|
+
elsif type == "g"
|
26
|
+
create_gauge(:name => name, :value => (value.to_i || 1) * (1.0 / (sample_rate || 1).to_f) )
|
27
|
+
elsif type == "c"
|
28
|
+
create_counter(:name => name, :value => (value.to_i || 1) * (1.0 / (sample_rate || 1).to_f) )
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise ParserError, "Parser Error - Invalid Stat: #{str}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_counter(attributes)
|
36
|
+
self.new(attributes.merge(:type => 'c'))
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_gauge(attributes)
|
40
|
+
self.new(attributes.merge(:type => 'g'))
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_timing(attributes)
|
44
|
+
self.new(attributes.merge(:type => 'ms'))
|
45
|
+
end
|
46
|
+
|
47
|
+
def aggregate(stats_array, name = nil)
|
48
|
+
raise NonMatchingTypesError unless stats_array.group_by { |stats| stats.type }.size == 1
|
49
|
+
|
50
|
+
result_stat = stats_array.first.dup
|
51
|
+
result_stat.name = name if name
|
52
|
+
if stats_array.first.counter?
|
53
|
+
result_stat.value = stats_array.map { |stats| stats.value }.inject(0) { |result, value| result += value }
|
54
|
+
result_stat
|
55
|
+
elsif stats_array.first.gauge?
|
56
|
+
total_value = stats_array.map { |stats| stats.value }.inject(0) { |result, value| result += value }
|
57
|
+
result_stat.value = total_value / stats_array.size
|
58
|
+
result_stat
|
59
|
+
elsif stats_array.first.timing?
|
60
|
+
# TODO implement timing aggregation
|
61
|
+
elsif stats_array.first.event?
|
62
|
+
# TODO implement event aggregation
|
63
|
+
else
|
64
|
+
raise ArgumentError, "Unknown data point type"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def aggregate_array(stats_array, name = nil)
|
69
|
+
raise NonMatchingTypesError unless stats_array.group_by { |stats| stats.type }.size == 1
|
70
|
+
|
71
|
+
if stats_array.first.counter?
|
72
|
+
tmp_hash = ts_hash_aggregated(stats_array) do |value1, value2|
|
73
|
+
value1 + value2
|
74
|
+
end
|
75
|
+
|
76
|
+
result = []
|
77
|
+
tmp_hash.each_pair do |key, value|
|
78
|
+
result << self.new(:name => name, :ts => key, :value => value, :type => 'c')
|
79
|
+
end
|
80
|
+
result
|
81
|
+
elsif stats_array.first.gauge?
|
82
|
+
tmp_hash = ts_hash_aggregated(stats_array) do |value1, value2|
|
83
|
+
(value1 + value2)/2
|
84
|
+
end
|
85
|
+
|
86
|
+
result = []
|
87
|
+
tmp_hash.each_pair do |key, value|
|
88
|
+
result << self.new(:name => name, :ts => key, :value => value, :type => 'g')
|
89
|
+
end
|
90
|
+
result
|
91
|
+
elsif stats_array.first.timing?
|
92
|
+
# TODO implement timing aggregation
|
93
|
+
elsif stats_array.first.event?
|
94
|
+
# TODO implement event aggregation
|
95
|
+
else
|
96
|
+
raise ArgumentError, "Unknown data point type"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_from_db(attributes)
|
101
|
+
self.new(:name => attributes["name"], :value => attributes["value"], :ts => attributes["ts"], :type => attributes["type"])
|
102
|
+
end
|
103
|
+
|
104
|
+
def ts_hash(query_result)
|
105
|
+
query_result.inject({}) { |result, dp| result[dp.ts] = dp; result }
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def ts_hash_aggregated(data_points, &block)
|
111
|
+
tmp = {}
|
112
|
+
data_points.each do |dp|
|
113
|
+
if tmp.key?(dp.ts)
|
114
|
+
tmp[dp.ts] = block.call(tmp[dp.ts], dp.value)
|
115
|
+
else
|
116
|
+
tmp[dp.ts] = dp.value
|
117
|
+
end
|
118
|
+
end
|
119
|
+
tmp
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
attr_accessor :name, :ts, :type, :value
|
124
|
+
|
125
|
+
def initialize(attributes)
|
126
|
+
@name = attributes[:name]
|
127
|
+
@value = attributes[:value]
|
128
|
+
@ts = attributes[:ts]
|
129
|
+
@type = attributes[:type]
|
130
|
+
end
|
131
|
+
|
132
|
+
def counter?
|
133
|
+
type == 'c'
|
134
|
+
end
|
135
|
+
|
136
|
+
def gauge?
|
137
|
+
type == 'g'
|
138
|
+
end
|
139
|
+
|
140
|
+
def timing?
|
141
|
+
type == 'ms'
|
142
|
+
end
|
143
|
+
|
144
|
+
def timestamp
|
145
|
+
ts
|
146
|
+
end
|
147
|
+
|
148
|
+
def value
|
149
|
+
@value.to_i if @value
|
150
|
+
end
|
151
|
+
|
152
|
+
def attributes
|
153
|
+
{
|
154
|
+
:name => name,
|
155
|
+
:value => value,
|
156
|
+
:ts => ts,
|
157
|
+
:type => type
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module SimpleMetrics
|
2
|
+
|
3
|
+
module Functions
|
4
|
+
|
5
|
+
# calculate the maximum value for multiple targets
|
6
|
+
#
|
7
|
+
# params:
|
8
|
+
# data_points [1, 3, 5], [2, 1, 6]
|
9
|
+
#
|
10
|
+
# return:
|
11
|
+
# array [2, 3, 6]
|
12
|
+
def max(*data_points)
|
13
|
+
end
|
14
|
+
|
15
|
+
# calculate the minimum value for multiple targets
|
16
|
+
def min(*data_points)
|
17
|
+
end
|
18
|
+
|
19
|
+
# add offset to each value
|
20
|
+
def offset(*data_points)
|
21
|
+
end
|
22
|
+
|
23
|
+
# multiple each value
|
24
|
+
def scale(*data_points)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return sum of all databounds
|
28
|
+
#
|
29
|
+
# params:
|
30
|
+
# data_points [1, 3, 5], [2, 1, 6]
|
31
|
+
#
|
32
|
+
# return:
|
33
|
+
# array [3, 4, 11]
|
34
|
+
def sum(*data_points)
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Other ideas
|
39
|
+
#
|
40
|
+
# as_percent:
|
41
|
+
# * calculate percentage of given targets
|
42
|
+
# * sum of all data_points will be used as the total 100% marker
|
43
|
+
#
|
44
|
+
# average_above (param):
|
45
|
+
# * return data_points with average value above given param
|
46
|
+
#
|
47
|
+
# average_below (param):
|
48
|
+
# * return data_points with average value below given param
|
49
|
+
#
|
50
|
+
# average_series:
|
51
|
+
# * return average value of given multiple targets
|
52
|
+
#
|
53
|
+
# current_above (param):
|
54
|
+
# * return data_points with value above given param
|
55
|
+
#
|
56
|
+
# current_below (param):
|
57
|
+
# * return data_points with value below given param
|
58
|
+
#
|
59
|
+
# derivative:
|
60
|
+
# * take an absolute value based target and show how many requests per min were handled
|
61
|
+
#
|
62
|
+
# integral:
|
63
|
+
# * calculate sum over time for relative values collected per minute
|
64
|
+
#
|
65
|
+
# logarithm:
|
66
|
+
# * calculate the value with log n (base 10 default)
|
67
|
+
#
|
68
|
+
|
69
|
+
end # module Functions
|
70
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module SimpleMetrics
|
2
|
+
|
3
|
+
#
|
4
|
+
# url format examples:
|
5
|
+
# * target=com.post.clicks (1 line in graph)
|
6
|
+
# * target=com.post.clicks.text&target=com.post.clicks.logo (2 lines in graph)
|
7
|
+
# * target=com.post.clicks.* (1 aggregated line in graph)
|
8
|
+
#
|
9
|
+
module Graph
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def minutes
|
13
|
+
Bucket[0]
|
14
|
+
end
|
15
|
+
|
16
|
+
def hours
|
17
|
+
Bucket[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def day
|
21
|
+
Bucket[2]
|
22
|
+
end
|
23
|
+
|
24
|
+
def week
|
25
|
+
Bucket[3]
|
26
|
+
end
|
27
|
+
|
28
|
+
def query_all(bucket, from, to, *targets)
|
29
|
+
result = {}
|
30
|
+
Array(targets).each do |target|
|
31
|
+
result[target.inspect] = values_only(query(bucket, from, to, target))
|
32
|
+
end
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
def query(bucket, from, to, target)
|
37
|
+
if target.is_a?(Regexp)
|
38
|
+
result = bucket.find_all_in_ts_range_by_regexp(from, to, target)
|
39
|
+
result = DataPoint.aggregate_array(result, target.inspect)
|
40
|
+
bucket.fill_gaps(from, to, result)
|
41
|
+
elsif target.is_a?(String) && target.include?('*')
|
42
|
+
result = bucket.find_all_in_ts_range_by_wildcard(from, to, target)
|
43
|
+
result = DataPoint.aggregate_array(result, target)
|
44
|
+
bucket.fill_gaps(from, to, result)
|
45
|
+
elsif target.is_a?(String)
|
46
|
+
result = bucket.find_all_in_ts_range_by_name(from, to, target)
|
47
|
+
bucket.fill_gaps(from, to, result)
|
48
|
+
else
|
49
|
+
raise ArgumentError, "Unknown target: #{target.inspect}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def values_only(data_point_array)
|
54
|
+
data_point_array.map { |data| { :ts => data.ts, :value => data.value } }
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
data/lib/simple_metrics/mongo.rb
CHANGED
@@ -12,6 +12,7 @@ module SimpleMetrics
|
|
12
12
|
db.create_collection(bucket.name, :capped => bucket.capped, :size => bucket.size)
|
13
13
|
SimpleMetrics.logger.debug "SERVER: MongoDB - created collection #{bucket.name}, capped: #{bucket.capped}, size: #{bucket.size}"
|
14
14
|
end
|
15
|
+
|
15
16
|
db.collection(bucket.name).ensure_index([['ts', ::Mongo::ASCENDING]])
|
16
17
|
SimpleMetrics.logger.debug "SERVER: MongoDB - ensure index on column ts for collection #{bucket.name}"
|
17
18
|
end
|
@@ -36,7 +37,7 @@ module SimpleMetrics
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def connection
|
39
|
-
@@connection ||= ::Mongo::Connection.new(SimpleMetrics.db_config[:host])
|
40
|
+
@@connection ||= ::Mongo::Connection.new(SimpleMetrics.db_config[:host], SimpleMetrics.db_config[:port])
|
40
41
|
end
|
41
42
|
|
42
43
|
def db
|
@@ -5,18 +5,18 @@ module SimpleMetrics
|
|
5
5
|
|
6
6
|
module ClientHandler
|
7
7
|
|
8
|
-
@@
|
8
|
+
@@data_points = []
|
9
9
|
|
10
10
|
class << self
|
11
|
-
def
|
12
|
-
|
13
|
-
@@
|
14
|
-
|
11
|
+
def get_and_clear_data_points
|
12
|
+
data_points = @@data_points.dup
|
13
|
+
@@data_points = []
|
14
|
+
data_points
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
@@
|
18
|
+
def data_points
|
19
|
+
@@data_points
|
20
20
|
end
|
21
21
|
|
22
22
|
def post_init
|
@@ -26,9 +26,9 @@ module SimpleMetrics
|
|
26
26
|
def receive_data(data)
|
27
27
|
SimpleMetrics.logger.debug "received_data: #{data.inspect}"
|
28
28
|
|
29
|
-
@@
|
30
|
-
@@
|
31
|
-
rescue
|
29
|
+
@@data_points ||= []
|
30
|
+
@@data_points << DataPoint.parse(data)
|
31
|
+
rescue DataPoint::ParserError => e
|
32
32
|
SimpleMetrics.logger.debug "Invalid Data skipped: #{data}"
|
33
33
|
end
|
34
34
|
end
|
@@ -47,7 +47,7 @@ module SimpleMetrics
|
|
47
47
|
EventMachine::add_periodic_timer(SimpleMetrics.config[:flush_interval]) do
|
48
48
|
SimpleMetrics.logger.debug "SERVER: period timer triggered after #{SimpleMetrics.config[:flush_interval]} seconds"
|
49
49
|
|
50
|
-
EM.defer { Bucket.
|
50
|
+
EM.defer { Bucket.flush_data_points(ClientHandler.get_and_clear_data_points) }
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
data/lib/simple_metrics.rb
CHANGED
@@ -4,8 +4,10 @@ require "logger"
|
|
4
4
|
require "simple_metrics/version"
|
5
5
|
require "simple_metrics/client"
|
6
6
|
require "simple_metrics/server"
|
7
|
-
require "simple_metrics/
|
7
|
+
require "simple_metrics/data_point"
|
8
8
|
require "simple_metrics/bucket"
|
9
|
+
require "simple_metrics/graph"
|
10
|
+
require "simple_metrics/functions"
|
9
11
|
require "simple_metrics/mongo"
|
10
12
|
|
11
13
|
module SimpleMetrics
|
@@ -76,12 +78,14 @@ module SimpleMetrics
|
|
76
78
|
|
77
79
|
DB_CONFIG_DEFAULTS = {
|
78
80
|
:host => 'localhost',
|
81
|
+
:port => 27017,
|
79
82
|
:prefix => 'development'
|
80
83
|
}.freeze
|
81
84
|
|
82
85
|
def db_config=(options)
|
83
86
|
@@db_config = {
|
84
|
-
:host => options.delete(:host),
|
87
|
+
:host => options.delete(:host) || 'localhost',
|
88
|
+
:port => options.delete(:port) || 27017,
|
85
89
|
:db_name => "simple_metrics_#{options.delete(:prefix)}",
|
86
90
|
:options => MONGODB_DEFAULTS.merge(options)
|
87
91
|
}
|
data/simple_metrics.gemspec
CHANGED
data/spec/bucket_spec.rb
CHANGED
@@ -43,7 +43,7 @@ module SimpleMetrics
|
|
43
43
|
end
|
44
44
|
|
45
45
|
let(:stats) do
|
46
|
-
|
46
|
+
DataPoint.create_counter(:name => "key1", :value => 5)
|
47
47
|
end
|
48
48
|
|
49
49
|
it "saves given data in bucket" do
|
@@ -71,9 +71,9 @@ module SimpleMetrics
|
|
71
71
|
|
72
72
|
describe "#find_all_by_name" do
|
73
73
|
it "returns all stats for given name" do
|
74
|
-
stats_same1 =
|
75
|
-
stats_same2 =
|
76
|
-
stats_different =
|
74
|
+
stats_same1 = DataPoint.create_counter(:name => "key1", :value => 5)
|
75
|
+
stats_same2 = DataPoint.create_counter(:name => "key1", :value => 3)
|
76
|
+
stats_different = DataPoint.create_counter(:name => "key2", :value => 3)
|
77
77
|
|
78
78
|
bucket.save(stats_same1, ts)
|
79
79
|
bucket.save(stats_same2, ts)
|
@@ -87,8 +87,8 @@ module SimpleMetrics
|
|
87
87
|
|
88
88
|
describe "#find_all_in_ts" do
|
89
89
|
it "returns all stats in given timestamp" do
|
90
|
-
stats1 =
|
91
|
-
stats2 =
|
90
|
+
stats1 = DataPoint.create_counter(:name => "key1", :value => 5)
|
91
|
+
stats2 = DataPoint.create_counter(:name => "key2", :value => 3)
|
92
92
|
|
93
93
|
bucket.save(stats1, ts)
|
94
94
|
bucket.save(stats2, bucket.next_ts_bucket(ts))
|
@@ -105,10 +105,10 @@ module SimpleMetrics
|
|
105
105
|
|
106
106
|
describe "#find_all_in_ts_by_name" do
|
107
107
|
it "returns all stats for given name and timestamp" do
|
108
|
-
stats1a =
|
109
|
-
stats1b =
|
110
|
-
stats2 =
|
111
|
-
stats1_different_ts =
|
108
|
+
stats1a = DataPoint.create_counter(:name => "key1", :value => 5)
|
109
|
+
stats1b = DataPoint.create_counter(:name => "key1", :value => 7)
|
110
|
+
stats2 = DataPoint.create_counter(:name => "key2", :value => 7)
|
111
|
+
stats1_different_ts = DataPoint.create_counter(:name => "key1", :value => 3)
|
112
112
|
|
113
113
|
bucket.save(stats1a, ts)
|
114
114
|
bucket.save(stats1b, ts)
|
@@ -122,6 +122,51 @@ module SimpleMetrics
|
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
125
|
+
describe "#find_all_in_ts_by_wildcard" do
|
126
|
+
it "returns all stats for given name and timestamp" do
|
127
|
+
stats1 = DataPoint.create_counter(:name => "com.test.key1", :value => 5)
|
128
|
+
stats2 = DataPoint.create_counter(:name => "com.test.key2", :value => 7)
|
129
|
+
stats_different = DataPoint.create_counter(:name => "com.test2.key1", :value => 3)
|
130
|
+
|
131
|
+
from = bucket.ts_bucket(ts)
|
132
|
+
to = from
|
133
|
+
bucket.save(stats1, ts)
|
134
|
+
bucket.save(stats2, ts)
|
135
|
+
bucket.save(stats_different, ts)
|
136
|
+
|
137
|
+
results = bucket.find_all_in_ts_range_by_wildcard(from, to, "com.test.*")
|
138
|
+
|
139
|
+
results.should have(2).items
|
140
|
+
results.first.name.should == "com.test.key1"
|
141
|
+
results.last.name.should == "com.test.key2"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "#fill_gaps" do
|
146
|
+
it "returns stats and fills missing gaps with null entries" do
|
147
|
+
stats = DataPoint.create_counter(:name => "com.test.key1", :value => 5)
|
148
|
+
|
149
|
+
from = ts - 10
|
150
|
+
to = ts + 10
|
151
|
+
bucket.save(stats, ts)
|
152
|
+
ts_bucket = bucket.ts_bucket(ts)
|
153
|
+
|
154
|
+
results = bucket.fill_gaps(from, to, [stats])
|
155
|
+
|
156
|
+
results.should have(3).items
|
157
|
+
results[0].name.should == "com.test.key1"
|
158
|
+
results[1].name.should == "com.test.key1"
|
159
|
+
results[2].name.should == "com.test.key1"
|
160
|
+
|
161
|
+
results[0].value.should be_nil
|
162
|
+
results[1].value.should == 5
|
163
|
+
results[2].value.should be_nil
|
164
|
+
|
165
|
+
results[0].ts.should == ts_bucket - 10
|
166
|
+
results[1].ts.should == ts_bucket
|
167
|
+
results[2].ts.should == ts_bucket + 10
|
168
|
+
end
|
169
|
+
end
|
125
170
|
end # describe "finder methods"
|
126
171
|
|
127
172
|
describe "#aggregate_all" do
|
@@ -130,10 +175,10 @@ module SimpleMetrics
|
|
130
175
|
Mongo.ensure_collections_exist
|
131
176
|
end
|
132
177
|
|
133
|
-
it "aggregates all
|
134
|
-
stats1a =
|
135
|
-
stats1b =
|
136
|
-
stats2 =
|
178
|
+
it "aggregates all counter data points" do
|
179
|
+
stats1a = DataPoint.create_counter(:name => "key1", :value => 5)
|
180
|
+
stats1b = DataPoint.create_counter(:name => "key1", :value => 7)
|
181
|
+
stats2 = DataPoint.create_counter(:name => "key2", :value => 3)
|
137
182
|
|
138
183
|
bucket2 = Bucket[1]
|
139
184
|
ts_at_insert = bucket2.previous_ts_bucket(ts)
|
@@ -154,28 +199,75 @@ module SimpleMetrics
|
|
154
199
|
key2_result.value.should == 3
|
155
200
|
key2_result.should be_counter
|
156
201
|
end
|
202
|
+
|
203
|
+
it "aggregates all gauge data points" do
|
204
|
+
stats1a = DataPoint.create_gauge(:name => "key1", :value => 5)
|
205
|
+
stats1b = DataPoint.create_gauge(:name => "key1", :value => 7)
|
206
|
+
stats2 = DataPoint.create_gauge(:name => "key2", :value => 3)
|
207
|
+
|
208
|
+
bucket2 = Bucket[1]
|
209
|
+
ts_at_insert = bucket2.previous_ts_bucket(ts)
|
210
|
+
bucket.save(stats1a, ts_at_insert)
|
211
|
+
bucket.save(stats1b, ts_at_insert)
|
212
|
+
bucket.save(stats2, ts_at_insert)
|
213
|
+
|
214
|
+
Bucket.aggregate_all(ts)
|
215
|
+
|
216
|
+
results = bucket2.find_all_in_ts(ts_at_insert)
|
217
|
+
results.should have(2).items
|
218
|
+
|
219
|
+
key1_result = results.find {|stat| stat.name == "key1"}
|
220
|
+
key1_result.value.should == 6
|
221
|
+
key1_result.should be_gauge
|
222
|
+
|
223
|
+
key2_result = results.find {|stat| stat.name == "key2"}
|
224
|
+
key2_result.value.should == 3
|
225
|
+
key2_result.should be_gauge
|
226
|
+
end
|
227
|
+
|
157
228
|
end # describe "#aggregate_all"
|
158
229
|
|
159
|
-
describe "#
|
230
|
+
describe "#flush_data_points" do
|
160
231
|
before do
|
161
|
-
|
162
|
-
|
163
|
-
|
232
|
+
Mongo.truncate_collections
|
233
|
+
Mongo.ensure_collections_exist
|
234
|
+
|
235
|
+
stats1 = DataPoint.create_counter(:name => "key1", :value => 5)
|
236
|
+
stats2 = DataPoint.create_counter(:name => "key1", :value => 7)
|
237
|
+
stats3 = DataPoint.create_counter(:name => "key2", :value => 3)
|
164
238
|
@stats = [stats1, stats2, stats3]
|
165
239
|
end
|
166
240
|
|
167
241
|
it "saves all stats in finest/first bucket" do
|
168
|
-
Bucket.
|
242
|
+
Bucket.flush_data_points(@stats)
|
169
243
|
|
170
244
|
results = bucket.find_all_in_ts(ts)
|
171
|
-
results.should have(
|
245
|
+
results.should have(2).items
|
172
246
|
end
|
173
247
|
|
174
248
|
it "calls aggregate_all afterwards" do
|
175
249
|
mock(Bucket).aggregate_all(ts)
|
176
|
-
Bucket.
|
250
|
+
Bucket.flush_data_points(@stats)
|
251
|
+
end
|
252
|
+
|
253
|
+
it "saves all stats and aggregate if duplicates found" do
|
254
|
+
Bucket.flush_data_points(@stats)
|
255
|
+
|
256
|
+
results = bucket.find_all_in_ts(ts)
|
257
|
+
results.should have(2).items
|
258
|
+
results.first.name.should == "key1"
|
259
|
+
results.last.name.should == "key2"
|
260
|
+
results.first.value == 12
|
261
|
+
results.last.value == 3
|
177
262
|
end
|
178
|
-
|
263
|
+
|
264
|
+
it "raises error if name matches but type does not" do
|
265
|
+
stats4 = DataPoint.create_gauge(:name => "key1", :value => 3)
|
266
|
+
input = @stats + [stats4]
|
267
|
+
expect { Bucket.flush_data_points(input) }.to raise_error(SimpleMetrics::DataPoint::NonMatchingTypesError)
|
268
|
+
end
|
269
|
+
|
270
|
+
end # describe "#flush_data_points"
|
179
271
|
|
180
272
|
end
|
181
273
|
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
module SimpleMetrics
|
5
|
+
|
6
|
+
describe Bucket do
|
7
|
+
|
8
|
+
let(:ts) do
|
9
|
+
Time.now.utc.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#parse" do
|
13
|
+
|
14
|
+
it "parses increment counter" do
|
15
|
+
stats = DataPoint.parse("com.example.test1:1|c")
|
16
|
+
stats.name.should == "com.example.test1"
|
17
|
+
stats.value.should == 1
|
18
|
+
stats.should be_counter
|
19
|
+
end
|
20
|
+
|
21
|
+
it "parses decrement counter" do
|
22
|
+
stats = DataPoint.parse("com.example.test1:-1|c")
|
23
|
+
stats.name.should == "com.example.test1"
|
24
|
+
stats.value.should == -1
|
25
|
+
stats.should be_counter
|
26
|
+
end
|
27
|
+
|
28
|
+
it "parses counter with sample rate" do
|
29
|
+
stats = DataPoint.parse("com.example.test2:5|c|@0.1")
|
30
|
+
stats.name.should == "com.example.test2"
|
31
|
+
stats.value.should == 50
|
32
|
+
stats.should be_counter
|
33
|
+
end
|
34
|
+
|
35
|
+
it "parses increment gauge" do
|
36
|
+
stats = DataPoint.parse("com.example.test3:5|g")
|
37
|
+
stats.name.should == "com.example.test3"
|
38
|
+
stats.value.should == 5
|
39
|
+
stats.should be_gauge
|
40
|
+
end
|
41
|
+
|
42
|
+
it "parses increment gauge with sample rate" do
|
43
|
+
stats = DataPoint.parse("com.example.test3:5|g|@0.1")
|
44
|
+
stats.name.should == "com.example.test3"
|
45
|
+
stats.value.should == 50
|
46
|
+
stats.should be_gauge
|
47
|
+
end
|
48
|
+
|
49
|
+
it "parses increment timing" do
|
50
|
+
stats = DataPoint.parse("com.example.test4:44|ms")
|
51
|
+
stats.name.should == "com.example.test4"
|
52
|
+
stats.value.should == 44
|
53
|
+
stats.should be_timing
|
54
|
+
end
|
55
|
+
|
56
|
+
it "parses increment timing with sample rate" do
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
describe "#aggregate" do
|
62
|
+
it "aggregates counter data points" do
|
63
|
+
stats1 = DataPoint.create_counter(:name => "key1", :value => 5)
|
64
|
+
stats2 = DataPoint.create_counter(:name => "key1", :value => 7)
|
65
|
+
result = DataPoint.aggregate([stats1, stats2])
|
66
|
+
result.value.should == 12
|
67
|
+
result.name.should == "key1"
|
68
|
+
result.should be_counter
|
69
|
+
end
|
70
|
+
|
71
|
+
it "aggregates counter data points with custom name" do
|
72
|
+
stats1 = DataPoint.create_counter(:name => "key1", :value => 5)
|
73
|
+
stats2 = DataPoint.create_counter(:name => "key1", :value => 7)
|
74
|
+
result = DataPoint.aggregate([stats1, stats2], "new_name")
|
75
|
+
result.value.should == 12
|
76
|
+
result.name.should == "new_name"
|
77
|
+
result.should be_counter
|
78
|
+
end
|
79
|
+
|
80
|
+
it "aggregates gauge data points" do
|
81
|
+
stats1 = DataPoint.create_gauge(:name => "key1", :value => 5)
|
82
|
+
stats2 = DataPoint.create_gauge(:name => "key1", :value => 7)
|
83
|
+
result = DataPoint.aggregate([stats1, stats2])
|
84
|
+
result.value.should == 6
|
85
|
+
result.name.should == "key1"
|
86
|
+
result.should be_gauge
|
87
|
+
end
|
88
|
+
|
89
|
+
it "aggregates timing data points" do
|
90
|
+
end
|
91
|
+
|
92
|
+
it "aggregates event data points" do
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#aggregate_array" do
|
97
|
+
it "aggregates counter data points" do
|
98
|
+
stats1 = DataPoint.create_counter(:name => "com.test.key1", :value => 5, :ts => ts)
|
99
|
+
stats2 = DataPoint.create_counter(:name => "com.test.key1", :value => 7, :ts => ts)
|
100
|
+
stats3 = DataPoint.create_counter(:name => "com.test.key1", :value => 9, :ts => (ts + 60) )
|
101
|
+
|
102
|
+
results = DataPoint.aggregate_array([stats1, stats2, stats3], "com.test.*")
|
103
|
+
results.should have(2).data_points
|
104
|
+
results.first.name.should == "com.test.*"
|
105
|
+
results.last.name.should == "com.test.*"
|
106
|
+
results.first.value.should == 12
|
107
|
+
results.last.value.should == 9
|
108
|
+
end
|
109
|
+
|
110
|
+
it "aggregates gauge data points" do
|
111
|
+
stats1 = DataPoint.create_gauge(:name => "com.test.key1", :value => 5, :ts => ts)
|
112
|
+
stats2 = DataPoint.create_gauge(:name => "com.test.key1", :value => 7, :ts => ts)
|
113
|
+
stats3 = DataPoint.create_gauge(:name => "com.test.key1", :value => 9, :ts => (ts + 60) )
|
114
|
+
|
115
|
+
results = DataPoint.aggregate_array([stats1, stats2, stats3], "com.test.*")
|
116
|
+
results.should have(2).data_points
|
117
|
+
results.first.name.should == "com.test.*"
|
118
|
+
results.last.name.should == "com.test.*"
|
119
|
+
results.first.value.should == 6
|
120
|
+
results.last.value.should == 9
|
121
|
+
end
|
122
|
+
|
123
|
+
it "raises NonMatchingTypesError if types are different" do
|
124
|
+
stats1 = DataPoint.create_counter(:name => "com.test.key1", :value => 5, :ts => ts)
|
125
|
+
stats2 = DataPoint.create_gauge(:name => "com.test.key1", :value => 5, :ts => ts)
|
126
|
+
expect { DataPoint.aggregate_array([stats1, stats2], "com.test.*") }.to raise_error(SimpleMetrics::DataPoint::NonMatchingTypesError)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
data/spec/graph_spec.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
module SimpleMetrics
|
5
|
+
|
6
|
+
describe Graph do
|
7
|
+
|
8
|
+
let(:ts) do
|
9
|
+
Time.now.utc.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:bucket) do
|
13
|
+
Bucket.first
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
Mongo.truncate_collections
|
18
|
+
Mongo.ensure_collections_exist
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#query" do
|
22
|
+
|
23
|
+
it "returns string request data points as is" do
|
24
|
+
dp1 = DataPoint.create_counter(:name => "key1", :value => 5)
|
25
|
+
|
26
|
+
bucket.save(dp1, ts)
|
27
|
+
|
28
|
+
current_ts = bucket.ts_bucket(ts)
|
29
|
+
from = current_ts
|
30
|
+
to = current_ts
|
31
|
+
results = Graph.query(bucket, from, to, "key1")
|
32
|
+
|
33
|
+
results.should have(1).data_point
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns string request data points and fill graps" do
|
37
|
+
dp1 = DataPoint.create_counter(:name => "key1", :value => 5)
|
38
|
+
|
39
|
+
bucket.save(dp1, ts)
|
40
|
+
|
41
|
+
current_ts = bucket.ts_bucket(ts)
|
42
|
+
from = current_ts
|
43
|
+
to = current_ts+10
|
44
|
+
results = Graph.query(bucket, from, to, "key1")
|
45
|
+
|
46
|
+
results.should have(2).data_point
|
47
|
+
results.first.value.should == 5
|
48
|
+
results.last.value.should be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it "returns wildcard request data points with aggregate counter" do
|
52
|
+
dp1 = DataPoint.create_counter(:name => "com.test.key1", :value => 5)
|
53
|
+
dp2 = DataPoint.create_counter(:name => "com.test.key2", :value => 7)
|
54
|
+
|
55
|
+
bucket.save(dp1, ts)
|
56
|
+
bucket.save(dp2, ts)
|
57
|
+
|
58
|
+
current_ts = bucket.ts_bucket(ts)
|
59
|
+
from = current_ts
|
60
|
+
to = current_ts
|
61
|
+
results = Graph.query(bucket, from, to, "com.test.*")
|
62
|
+
results.should have(1).data_point
|
63
|
+
result = results.first
|
64
|
+
result.name.should == "com.test.*"
|
65
|
+
result.value.should == 12
|
66
|
+
result.should be_counter
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns regexp request data points with aggregate gauge" do
|
70
|
+
dp1 = DataPoint.create_gauge(:name => "com.test.key1", :value => 5)
|
71
|
+
dp2 = DataPoint.create_gauge(:name => "com.test.key2", :value => 7)
|
72
|
+
|
73
|
+
bucket.save(dp1, ts)
|
74
|
+
bucket.save(dp2, ts)
|
75
|
+
|
76
|
+
current_ts = bucket.ts_bucket(ts)
|
77
|
+
from = current_ts
|
78
|
+
to = current_ts
|
79
|
+
|
80
|
+
results = Graph.query(bucket, from, to, /com\.test(.*)/)
|
81
|
+
results.should have(1).data_point
|
82
|
+
result = results.first
|
83
|
+
result.name.should == "/com\\.test(.*)/"
|
84
|
+
result.value.should == 6
|
85
|
+
result.should be_gauge
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_metrics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-04-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70101883011000 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70101883011000
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70101883009820 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70101883009820
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rr
|
38
|
-
requirement: &
|
38
|
+
requirement: &70101883009000 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70101883009000
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: eventmachine
|
49
|
-
requirement: &
|
49
|
+
requirement: &70101883008200 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70101883008200
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: daemons
|
60
|
-
requirement: &
|
60
|
+
requirement: &70101883007560 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,33 +65,34 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70101883007560
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: mongo
|
71
|
-
requirement: &
|
71
|
+
requirement: &70101883006620 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
|
-
- -
|
74
|
+
- - ~>
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
76
|
+
version: '1.6'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70101883006620
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
|
-
name:
|
82
|
-
requirement: &
|
81
|
+
name: bson
|
82
|
+
requirement: &70101883005800 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
|
-
- -
|
85
|
+
- - ~>
|
86
86
|
- !ruby/object:Gem::Version
|
87
|
-
version: '
|
87
|
+
version: '1.6'
|
88
88
|
type: :runtime
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70101883005800
|
91
91
|
description: SimpleMetrics
|
92
92
|
email:
|
93
93
|
- fdietz@gmail.com
|
94
94
|
executables:
|
95
|
+
- populate
|
95
96
|
- simple_metrics_client
|
96
97
|
- simple_metrics_server
|
97
98
|
extensions: []
|
@@ -102,20 +103,24 @@ files:
|
|
102
103
|
- Gemfile
|
103
104
|
- README.markdown
|
104
105
|
- Rakefile
|
106
|
+
- bin/populate
|
105
107
|
- bin/simple_metrics_client
|
106
108
|
- bin/simple_metrics_server
|
107
109
|
- example/increment.rb
|
108
110
|
- lib/simple_metrics.rb
|
109
111
|
- lib/simple_metrics/bucket.rb
|
110
112
|
- lib/simple_metrics/client.rb
|
113
|
+
- lib/simple_metrics/data_point.rb
|
114
|
+
- lib/simple_metrics/functions.rb
|
115
|
+
- lib/simple_metrics/graph.rb
|
111
116
|
- lib/simple_metrics/mongo.rb
|
112
117
|
- lib/simple_metrics/server.rb
|
113
|
-
- lib/simple_metrics/stats.rb
|
114
118
|
- lib/simple_metrics/version.rb
|
115
119
|
- simple_metrics.gemspec
|
116
120
|
- spec/bucket_spec.rb
|
121
|
+
- spec/data_point_spec.rb
|
122
|
+
- spec/graph_spec.rb
|
117
123
|
- spec/spec_helper.rb
|
118
|
-
- spec/stats_spec.rb
|
119
124
|
homepage: ''
|
120
125
|
licenses: []
|
121
126
|
post_install_message:
|
@@ -128,19 +133,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
133
|
- - ! '>='
|
129
134
|
- !ruby/object:Gem::Version
|
130
135
|
version: '0'
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
hash: 3607993347198601415
|
131
139
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
140
|
none: false
|
133
141
|
requirements:
|
134
142
|
- - ! '>='
|
135
143
|
- !ruby/object:Gem::Version
|
136
144
|
version: '0'
|
145
|
+
segments:
|
146
|
+
- 0
|
147
|
+
hash: 3607993347198601415
|
137
148
|
requirements: []
|
138
149
|
rubyforge_project:
|
139
|
-
rubygems_version: 1.8.
|
150
|
+
rubygems_version: 1.8.17
|
140
151
|
signing_key:
|
141
152
|
specification_version: 3
|
142
153
|
summary: SimpleMetrics
|
143
154
|
test_files:
|
144
155
|
- spec/bucket_spec.rb
|
156
|
+
- spec/data_point_spec.rb
|
157
|
+
- spec/graph_spec.rb
|
145
158
|
- spec/spec_helper.rb
|
146
|
-
- spec/stats_spec.rb
|
data/lib/simple_metrics/stats.rb
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module SimpleMetrics
|
3
|
-
|
4
|
-
class Stats
|
5
|
-
|
6
|
-
class NonMatchingTypesError < Exception; end
|
7
|
-
class ParserError < Exception; end
|
8
|
-
|
9
|
-
# examples:
|
10
|
-
# com.example.test1:1|c
|
11
|
-
# com.example.test2:-1|c
|
12
|
-
# com.example.test2:50|g
|
13
|
-
# com.example.test3:5|c|@0.1
|
14
|
-
# com.example.test4:44|ms
|
15
|
-
REGEXP = /^([\d\w_.]*):(-?[\d]*)\|(c|g|ms){1}(\|@([.\d]+))?$/i
|
16
|
-
|
17
|
-
class << self
|
18
|
-
|
19
|
-
def parse(str)
|
20
|
-
if str =~ REGEXP
|
21
|
-
name, value, type, sample_rate = $1, $2, $3, $5
|
22
|
-
if type == "ms"
|
23
|
-
# TODO: implement sample_rate handling
|
24
|
-
create_timing(:name => name, :value => value)
|
25
|
-
elsif type == "g"
|
26
|
-
create_gauge(:name => name, :value => (value.to_i || 1) * (1.0 / (sample_rate || 1).to_f) )
|
27
|
-
elsif type == "c"
|
28
|
-
create_counter(:name => name, :value => (value.to_i || 1) * (1.0 / (sample_rate || 1).to_f) )
|
29
|
-
end
|
30
|
-
else
|
31
|
-
raise ParserError, "Parser Error - Invalid Stat: #{str}"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def create_counter(attributes)
|
36
|
-
Stats.new(attributes.merge(:type => 'c'))
|
37
|
-
end
|
38
|
-
|
39
|
-
def create_gauge(attributes)
|
40
|
-
Stats.new(attributes.merge(:type => 'g'))
|
41
|
-
end
|
42
|
-
|
43
|
-
def create_timing(attributes)
|
44
|
-
Stats.new(attributes.merge(:type => 'ms'))
|
45
|
-
end
|
46
|
-
|
47
|
-
def aggregate(stats_array)
|
48
|
-
raise NonMatchingTypesError unless stats_array.group_by { |stats| stats.type }.size == 1
|
49
|
-
|
50
|
-
result_stat = stats_array.first.dup
|
51
|
-
result_stat.value = stats_array.map { |stats| stats.value }.inject(0) { |result, value| result += value }
|
52
|
-
result_stat
|
53
|
-
end
|
54
|
-
|
55
|
-
def create_from_db(attributes)
|
56
|
-
Stats.new(:name => attributes["name"], :value => attributes["value"], :ts => attributes["ts"], :type => attributes["type"])
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
attr_accessor :name, :ts, :type, :value
|
61
|
-
|
62
|
-
def initialize(attributes)
|
63
|
-
@name = attributes[:name]
|
64
|
-
@value = attributes[:value]
|
65
|
-
@ts = attributes[:ts]
|
66
|
-
@type = attributes[:type]
|
67
|
-
end
|
68
|
-
|
69
|
-
def counter?
|
70
|
-
type == 'c'
|
71
|
-
end
|
72
|
-
|
73
|
-
def gauge?
|
74
|
-
type == 'g'
|
75
|
-
end
|
76
|
-
|
77
|
-
def timing?
|
78
|
-
type == 'ms'
|
79
|
-
end
|
80
|
-
|
81
|
-
def timestamp
|
82
|
-
ts
|
83
|
-
end
|
84
|
-
|
85
|
-
def value
|
86
|
-
@value.to_i
|
87
|
-
end
|
88
|
-
|
89
|
-
def attributes
|
90
|
-
{
|
91
|
-
:name => name,
|
92
|
-
:value => value,
|
93
|
-
:ts => ts,
|
94
|
-
:type => type
|
95
|
-
}
|
96
|
-
end
|
97
|
-
|
98
|
-
end
|
99
|
-
end
|
data/spec/stats_spec.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require "spec_helper"
|
3
|
-
|
4
|
-
module SimpleMetrics
|
5
|
-
|
6
|
-
describe Bucket do
|
7
|
-
|
8
|
-
describe "#parse" do
|
9
|
-
|
10
|
-
it "parses increment counter" do
|
11
|
-
stats = Stats.parse("com.example.test1:1|c")
|
12
|
-
stats.name.should == "com.example.test1"
|
13
|
-
stats.value.should == 1
|
14
|
-
stats.should be_counter
|
15
|
-
end
|
16
|
-
|
17
|
-
it "parses decrement counter" do
|
18
|
-
stats = Stats.parse("com.example.test1:-1|c")
|
19
|
-
stats.name.should == "com.example.test1"
|
20
|
-
stats.value.should == -1
|
21
|
-
stats.should be_counter
|
22
|
-
end
|
23
|
-
|
24
|
-
it "parses counter with sample rate" do
|
25
|
-
stats = Stats.parse("com.example.test2:5|c|@0.1")
|
26
|
-
stats.name.should == "com.example.test2"
|
27
|
-
stats.value.should == 50
|
28
|
-
stats.should be_counter
|
29
|
-
end
|
30
|
-
|
31
|
-
it "parses increment gauge" do
|
32
|
-
stats = Stats.parse("com.example.test3:5|g")
|
33
|
-
stats.name.should == "com.example.test3"
|
34
|
-
stats.value.should == 5
|
35
|
-
stats.should be_gauge
|
36
|
-
end
|
37
|
-
|
38
|
-
it "parses increment gauge with sample rate" do
|
39
|
-
stats = Stats.parse("com.example.test3:5|g|@0.1")
|
40
|
-
stats.name.should == "com.example.test3"
|
41
|
-
stats.value.should == 50
|
42
|
-
stats.should be_gauge
|
43
|
-
end
|
44
|
-
|
45
|
-
it "parses increment timing" do
|
46
|
-
stats = Stats.parse("com.example.test4:44|ms")
|
47
|
-
stats.name.should == "com.example.test4"
|
48
|
-
stats.value.should == 44
|
49
|
-
stats.should be_timing
|
50
|
-
end
|
51
|
-
|
52
|
-
it "parses increment timing with sample rate" do
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
describe "create_counter" do
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|