simple_metrics 0.3.6 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|