simple_metrics 0.3.6 → 0.4.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/README.markdown +53 -16
- data/Rakefile +0 -17
- data/bin/populate +13 -26
- data/bin/simple_metrics_client +64 -0
- data/bin/simple_metrics_server +3 -4
- data/lib/simple_metrics.rb +76 -17
- data/lib/simple_metrics/bucket.rb +106 -43
- data/lib/simple_metrics/client.rb +83 -0
- data/lib/simple_metrics/data_point.rb +137 -45
- data/lib/simple_metrics/functions.rb +5 -5
- data/lib/simple_metrics/graph.rb +32 -69
- data/lib/simple_metrics/mongo.rb +48 -0
- data/lib/simple_metrics/server.rb +66 -0
- data/lib/simple_metrics/version.rb +1 -1
- data/simple_metrics.gemspec +0 -6
- data/spec/bucket_spec.rb +152 -17
- data/spec/data_point_spec.rb +64 -14
- data/spec/graph_spec.rb +19 -30
- data/spec/spec_helper.rb +3 -3
- metadata +24 -139
- data/.travis.yml +0 -3
- data/bin/simple_metrics_web +0 -11
- data/config.ru +0 -6
- data/default_config.yml +0 -34
- data/lib/simple_metrics/app.rb +0 -52
- data/lib/simple_metrics/configuration.rb +0 -97
- data/lib/simple_metrics/data_point/base.rb +0 -59
- data/lib/simple_metrics/data_point/counter.rb +0 -20
- data/lib/simple_metrics/data_point/event.rb +0 -16
- data/lib/simple_metrics/data_point/gauge.rb +0 -19
- data/lib/simple_metrics/data_point/timing.rb +0 -15
- data/lib/simple_metrics/data_point_repository.rb +0 -114
- data/lib/simple_metrics/importer.rb +0 -64
- data/lib/simple_metrics/metric.rb +0 -29
- data/lib/simple_metrics/metric_repository.rb +0 -54
- data/lib/simple_metrics/public/css/bootstrap-responsive.min.css +0 -12
- data/lib/simple_metrics/public/css/bootstrap.min.css +0 -689
- data/lib/simple_metrics/public/css/graph.css +0 -45
- data/lib/simple_metrics/public/css/rickshaw.min.css +0 -1
- data/lib/simple_metrics/public/img/glyphicons-halflings-white.png +0 -0
- data/lib/simple_metrics/public/img/glyphicons-halflings.png +0 -0
- data/lib/simple_metrics/public/js/app.js +0 -20
- data/lib/simple_metrics/public/js/collections/graph.js +0 -16
- data/lib/simple_metrics/public/js/collections/metric.js +0 -9
- data/lib/simple_metrics/public/js/helpers.js +0 -23
- data/lib/simple_metrics/public/js/lib/backbone-0.9.2.min.js +0 -38
- data/lib/simple_metrics/public/js/lib/bootstrap.min.js +0 -6
- data/lib/simple_metrics/public/js/lib/d3.v2.min.js +0 -4
- data/lib/simple_metrics/public/js/lib/handlebars-1.0.0.beta.6.js +0 -1550
- data/lib/simple_metrics/public/js/lib/jquery-1.7.1.min.js +0 -4
- data/lib/simple_metrics/public/js/lib/moment.min.js +0 -6
- data/lib/simple_metrics/public/js/lib/rickshaw.min.js +0 -1
- data/lib/simple_metrics/public/js/lib/underscore-1.3.1.min.js +0 -31
- data/lib/simple_metrics/public/js/models/graph.js +0 -6
- data/lib/simple_metrics/public/js/models/metric.js +0 -7
- data/lib/simple_metrics/public/js/router.js +0 -42
- data/lib/simple_metrics/public/js/views/app.js +0 -18
- data/lib/simple_metrics/public/js/views/dashboard.js +0 -10
- data/lib/simple_metrics/public/js/views/graph.js +0 -101
- data/lib/simple_metrics/public/js/views/metric.js +0 -82
- data/lib/simple_metrics/public/js/views/metrics.js +0 -10
- data/lib/simple_metrics/repository.rb +0 -34
- data/lib/simple_metrics/udp_server.rb +0 -81
- data/lib/simple_metrics/views/graph.erb +0 -93
- data/lib/simple_metrics/views/index.erb +0 -0
- data/lib/simple_metrics/views/layout.erb +0 -138
- data/lib/simple_metrics/views/show.erb +0 -31
- data/spec/data_point_repository_spec.rb +0 -77
- data/spec/importer_spec.rb +0 -126
- data/spec/metric_repository_spec.rb +0 -53
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "socket"
|
3
|
+
|
4
|
+
module SimpleMetrics
|
5
|
+
|
6
|
+
class Client
|
7
|
+
VERSION = "0.0.1"
|
8
|
+
|
9
|
+
def initialize(host, port = 8125)
|
10
|
+
@host, @port = host, port
|
11
|
+
end
|
12
|
+
|
13
|
+
# send relative value
|
14
|
+
def increment(stat, sample_rate = 1)
|
15
|
+
count(stat, 1, sample_rate)
|
16
|
+
end
|
17
|
+
|
18
|
+
# send relative value
|
19
|
+
def decrement(stat, sample_rate = 1)
|
20
|
+
count(stat, -1, sample_rate)
|
21
|
+
end
|
22
|
+
|
23
|
+
# send relative value
|
24
|
+
def count(stat, count, sample_rate = 1)
|
25
|
+
send_data( stat, count, 'c', sample_rate)
|
26
|
+
end
|
27
|
+
|
28
|
+
# send absolute value
|
29
|
+
# TODO: check if this is actually supported by Statsd server
|
30
|
+
def gauge(stat, value)
|
31
|
+
send_data(stat, value, 'g')
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sends a timing (in ms) (glork)
|
35
|
+
def timing(stat, ms, sample_rate = 1)
|
36
|
+
send_data(stat, ms, 'ms', sample_rate)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sends a timing (in ms) block based
|
40
|
+
def time(stat, sample_rate = 1, &block)
|
41
|
+
start = Time.now
|
42
|
+
result = block.call
|
43
|
+
timing(stat, ((Time.now - start) * 1000).round, sample_rate)
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def sampled(sample_rate, &block)
|
50
|
+
if sample_rate < 1
|
51
|
+
block.call if rand <= sample_rate
|
52
|
+
else
|
53
|
+
block.call
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def send_data(stat, delta, type, sample_rate = 1)
|
58
|
+
sampled(sample_rate) do
|
59
|
+
data = "#{stat}:#{delta}|#{type}" # TODO: check stat is valid
|
60
|
+
data << "|@#{sample_rate}" if sample_rate < 1
|
61
|
+
send_to_socket(data)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def send_to_socket(data)
|
66
|
+
logger.debug "SimpleMetrics Client send: #{data}"
|
67
|
+
socket.send(data, 0, @host, @port)
|
68
|
+
rescue Exception => e
|
69
|
+
puts e.backtrace
|
70
|
+
logger.error "SimpleMetrics Client error: #{e}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def socket
|
74
|
+
@socket ||= UDPSocket.new
|
75
|
+
end
|
76
|
+
|
77
|
+
def logger
|
78
|
+
@logger ||= SimpleMetrics.logger
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
module SimpleMetrics
|
2
|
-
|
3
|
-
|
3
|
+
|
4
|
+
class DataPoint
|
4
5
|
|
5
6
|
class NonMatchingTypesError < Exception; end
|
6
7
|
class ParserError < Exception; end
|
7
|
-
class UnknownTypeError < Exception; end
|
8
8
|
|
9
9
|
# examples:
|
10
10
|
# com.example.test1:1|c
|
@@ -14,57 +14,149 @@ module SimpleMetrics
|
|
14
14
|
# com.example.test4:44|ms
|
15
15
|
REGEXP = /^([\d\w_.]*):(-?[\d]*)\|(c|g|ms){1}(\|@([.\d]+))?$/i
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
23
33
|
end
|
24
|
-
end
|
25
34
|
|
26
|
-
|
27
|
-
|
28
|
-
when 'c'
|
29
|
-
Counter.new(attributes)
|
30
|
-
when 'g'
|
31
|
-
Gauge.new(attributes)
|
32
|
-
when 'ms'
|
33
|
-
Timing.new(attributes)
|
34
|
-
when 'ev'
|
35
|
-
Event.new(attributes)
|
36
|
-
else
|
37
|
-
raise UnknownTypeError, "Unknown Type Error: #{attributes[:type]}"
|
35
|
+
def create_counter(attributes)
|
36
|
+
self.new(attributes.merge(:type => 'c'))
|
38
37
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
55
98
|
end
|
56
|
-
|
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]
|
57
130
|
end
|
58
131
|
|
59
|
-
|
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
|
60
147
|
|
61
|
-
def
|
62
|
-
|
148
|
+
def value
|
149
|
+
@value.to_i if @value
|
63
150
|
end
|
64
151
|
|
65
|
-
def
|
66
|
-
|
152
|
+
def attributes
|
153
|
+
{
|
154
|
+
:name => name,
|
155
|
+
:value => value,
|
156
|
+
:ts => ts,
|
157
|
+
:type => type
|
158
|
+
}
|
67
159
|
end
|
68
160
|
|
69
161
|
end
|
70
|
-
end
|
162
|
+
end
|
@@ -2,14 +2,14 @@ module SimpleMetrics
|
|
2
2
|
|
3
3
|
module Functions
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
# calculate the maximum value for multiple targets
|
6
|
+
#
|
7
|
+
# params:
|
8
8
|
# data_points [1, 3, 5], [2, 1, 6]
|
9
9
|
#
|
10
10
|
# return:
|
11
11
|
# array [2, 3, 6]
|
12
|
-
|
12
|
+
def max(*data_points)
|
13
13
|
end
|
14
14
|
|
15
15
|
# calculate the minimum value for multiple targets
|
@@ -17,7 +17,7 @@ module SimpleMetrics
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# add offset to each value
|
20
|
-
|
20
|
+
def offset(*data_points)
|
21
21
|
end
|
22
22
|
|
23
23
|
# multiple each value
|
data/lib/simple_metrics/graph.rb
CHANGED
@@ -1,95 +1,58 @@
|
|
1
1
|
module SimpleMetrics
|
2
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
|
+
#
|
3
9
|
module Graph
|
4
10
|
extend self
|
5
11
|
|
6
|
-
def minutes
|
7
|
-
|
8
|
-
|
9
|
-
def week; Bucket[3]; end
|
12
|
+
def minutes
|
13
|
+
Bucket[0]
|
14
|
+
end
|
10
15
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
when 'day'
|
18
|
-
one_day
|
19
|
-
when 'week'
|
20
|
-
one_week
|
21
|
-
else
|
22
|
-
raise "Unknown time param: #{time}"
|
23
|
-
end
|
16
|
+
def hours
|
17
|
+
Bucket[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def day
|
21
|
+
Bucket[2]
|
24
22
|
end
|
25
|
-
|
23
|
+
|
24
|
+
def week
|
25
|
+
Bucket[3]
|
26
|
+
end
|
27
|
+
|
26
28
|
def query_all(bucket, from, to, *targets)
|
27
|
-
|
29
|
+
result = {}
|
28
30
|
Array(targets).each do |target|
|
29
|
-
|
31
|
+
result[target.inspect] = values_only(query(bucket, from, to, target))
|
30
32
|
end
|
31
|
-
|
33
|
+
result
|
32
34
|
end
|
33
35
|
|
34
36
|
def query(bucket, from, to, target)
|
35
|
-
if
|
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?('*')
|
36
42
|
result = bucket.find_all_in_ts_range_by_wildcard(from, to, target)
|
37
|
-
result =
|
43
|
+
result = DataPoint.aggregate_array(result, target)
|
38
44
|
bucket.fill_gaps(from, to, result)
|
39
45
|
elsif target.is_a?(String)
|
40
46
|
result = bucket.find_all_in_ts_range_by_name(from, to, target)
|
41
47
|
bucket.fill_gaps(from, to, result)
|
42
48
|
else
|
43
|
-
raise ArgumentError, "Unknown target
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def aggregate(dps, target)
|
50
|
-
raise SimpleMetrics::DataPoint::NonMatchingTypesError if has_non_matching_types?(dps)
|
51
|
-
|
52
|
-
tmp = {}
|
53
|
-
dps.each do |dp|
|
54
|
-
dp.name = target
|
55
|
-
if tmp.key?(dp.ts)
|
56
|
-
tmp[dp.ts] << dp
|
57
|
-
else
|
58
|
-
tmp[dp.ts] = [dp]
|
59
|
-
end
|
60
|
-
end
|
61
|
-
tmp
|
62
|
-
|
63
|
-
result = []
|
64
|
-
tmp.each_pair do |ts, dps|
|
65
|
-
result << DataPoint.aggregate_values(dps)
|
49
|
+
raise ArgumentError, "Unknown target: #{target.inspect}"
|
66
50
|
end
|
67
|
-
result
|
68
51
|
end
|
69
52
|
|
70
|
-
def
|
71
|
-
|
53
|
+
def values_only(data_point_array)
|
54
|
+
data_point_array.map { |data| { :ts => data.ts, :value => data.value } }
|
72
55
|
end
|
73
56
|
|
74
|
-
def one_minute
|
75
|
-
60
|
76
|
-
end
|
77
|
-
|
78
|
-
def one_hour
|
79
|
-
one_minute * 60
|
80
|
-
end
|
81
|
-
|
82
|
-
def one_day
|
83
|
-
one_hour * 24
|
84
|
-
end
|
85
|
-
|
86
|
-
def one_week
|
87
|
-
one_day * 7
|
88
|
-
end
|
89
|
-
|
90
|
-
def wild_card_query?(target)
|
91
|
-
target.is_a?(String) && target.include?('*')
|
92
|
-
end
|
93
|
-
|
94
57
|
end
|
95
58
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "mongo"
|
3
|
+
|
4
|
+
module SimpleMetrics
|
5
|
+
module Mongo
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def ensure_collections_exist
|
9
|
+
SimpleMetrics.logger.debug "SERVER: MongoDB - found following collections: #{db.collection_names.inspect}"
|
10
|
+
Bucket.all.each do |bucket|
|
11
|
+
unless db.collection_names.include?(bucket.name)
|
12
|
+
db.create_collection(bucket.name, :capped => bucket.capped, :size => bucket.size)
|
13
|
+
SimpleMetrics.logger.debug "SERVER: MongoDB - created collection #{bucket.name}, capped: #{bucket.capped}, size: #{bucket.size}"
|
14
|
+
end
|
15
|
+
|
16
|
+
db.collection(bucket.name).ensure_index([['ts', ::Mongo::ASCENDING]])
|
17
|
+
SimpleMetrics.logger.debug "SERVER: MongoDB - ensure index on column ts for collection #{bucket.name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def truncate_collections
|
22
|
+
Bucket.all.each do |bucket|
|
23
|
+
if db.collection_names.include?(bucket.name)
|
24
|
+
if bucket.capped?
|
25
|
+
collection(bucket.name).drop # capped collections can't remove elements, drop it instead
|
26
|
+
else
|
27
|
+
collection(bucket.name).remove
|
28
|
+
end
|
29
|
+
SimpleMetrics.logger.debug "SERVER: MongoDB - truncated collection #{bucket.name}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@@collection = {}
|
35
|
+
def collection(name)
|
36
|
+
@@collection[name] ||= db.collection(name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def connection
|
40
|
+
@@connection ||= ::Mongo::Connection.new(SimpleMetrics.db_config[:host], SimpleMetrics.db_config[:port])
|
41
|
+
end
|
42
|
+
|
43
|
+
def db
|
44
|
+
@@db ||= connection.db(SimpleMetrics.db_config[:db_name], SimpleMetrics.db_config[:options])
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|