simple_metrics 0.2.3 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +3 -0
- data/README.markdown +16 -53
- data/Rakefile +17 -0
- data/bin/populate +26 -13
- data/bin/simple_metrics_server +4 -3
- data/bin/simple_metrics_web +11 -0
- data/config.ru +6 -0
- data/default_config.yml +34 -0
- data/lib/simple_metrics/app.rb +136 -0
- data/lib/simple_metrics/array_aggregation.rb +84 -0
- data/lib/simple_metrics/bucket.rb +74 -63
- data/lib/simple_metrics/configuration.rb +79 -0
- data/lib/simple_metrics/data_point/base.rb +59 -0
- data/lib/simple_metrics/data_point/counter.rb +13 -0
- data/lib/simple_metrics/data_point/event.rb +12 -0
- data/lib/simple_metrics/data_point/gauge.rb +13 -0
- data/lib/simple_metrics/data_point/timing.rb +12 -0
- data/lib/simple_metrics/data_point.rb +32 -136
- data/lib/simple_metrics/data_point_repository.rb +131 -0
- data/lib/simple_metrics/functions.rb +5 -5
- data/lib/simple_metrics/graph.rb +10 -14
- data/lib/simple_metrics/metric.rb +28 -0
- data/lib/simple_metrics/metric_repository.rb +54 -0
- data/lib/simple_metrics/public/css/bootstrap-responsive.min.css +12 -0
- data/lib/simple_metrics/public/css/bootstrap.min.css +689 -0
- data/lib/simple_metrics/public/css/graph.css +45 -0
- data/lib/simple_metrics/public/css/rickshaw.min.css +1 -0
- 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/application.js +278 -0
- data/lib/simple_metrics/public/js/backbone-0.9.2.min.js +38 -0
- data/lib/simple_metrics/public/js/bootstrap.min.js +6 -0
- data/lib/simple_metrics/public/js/d3.v2.min.js +4 -0
- data/lib/simple_metrics/public/js/handlebars-1.0.0.beta.6.js +1550 -0
- data/lib/simple_metrics/public/js/jquery-1.7.1.min.js +4 -0
- data/lib/simple_metrics/public/js/rickshaw.min.js +1 -0
- data/lib/simple_metrics/public/js/underscore-1.3.1.min.js +31 -0
- data/lib/simple_metrics/repository.rb +34 -0
- data/lib/simple_metrics/udp_server.rb +81 -0
- data/lib/simple_metrics/update_aggregation.rb +62 -0
- data/lib/simple_metrics/value_aggregation.rb +63 -0
- data/lib/simple_metrics/version.rb +1 -1
- data/lib/simple_metrics/views/graph.erb +93 -0
- data/lib/simple_metrics/views/index.erb +0 -0
- data/lib/simple_metrics/views/layout.erb +119 -0
- data/lib/simple_metrics/views/show.erb +31 -0
- data/lib/simple_metrics.rb +19 -76
- data/simple_metrics.gemspec +6 -0
- data/spec/array_aggregation_spec.rb +51 -0
- data/spec/bucket_spec.rb +24 -62
- data/spec/data_point_repository_spec.rb +114 -0
- data/spec/data_point_spec.rb +1 -70
- data/spec/graph_spec.rb +2 -20
- data/spec/metric_repository_spec.rb +53 -0
- data/spec/spec_helper.rb +3 -3
- data/spec/value_aggregation_spec.rb +52 -0
- metadata +131 -24
- data/bin/simple_metrics_client +0 -64
- data/lib/simple_metrics/client.rb +0 -83
- data/lib/simple_metrics/mongo.rb +0 -48
- data/lib/simple_metrics/server.rb +0 -66
data/.travis.yml
ADDED
data/README.markdown
CHANGED
@@ -11,76 +11,39 @@ SimpleMetrics is written in Ruby and packaged as a gem.
|
|
11
11
|
|
12
12
|
The current version is considered ALPHA.
|
13
13
|
|
14
|
-
SimpleMetrics
|
14
|
+
SimpleMetrics Server
|
15
15
|
--------------------
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
Send a count of 5 for data point "module.test1":
|
20
|
-
|
21
|
-
simple_metrics_client module.test1 -counter 5
|
22
|
-
|
23
|
-
Send a timing of 100ms:
|
24
|
-
|
25
|
-
simple_metrics_client module.test1 -timing 100
|
26
|
-
|
27
|
-
doing the same, but since we expect a lot of calls we sample the data (10%):
|
28
|
-
|
29
|
-
simple_metrics_client module.test1 -timing 100 --sample_rate 0.1
|
30
|
-
|
31
|
-
more info:
|
32
|
-
|
33
|
-
simple_metrics_client --help
|
34
|
-
|
35
|
-
Ruby client API
|
36
|
-
---------------
|
37
|
-
|
38
|
-
Initialize client:
|
39
|
-
|
40
|
-
client = SimpleMetrics::Client.new("localhost")
|
41
|
-
|
42
|
-
sends "com.example.test1:1|c" via UDP:
|
43
|
-
|
44
|
-
client.increment("com.example.test1")
|
45
|
-
|
46
|
-
sends "com.example.test1:-1|c":
|
47
|
-
|
48
|
-
client.decrement("com.example.test1")
|
49
|
-
|
50
|
-
sends "com.example.test1:5|c" (a counter with a relative value of 5):
|
51
|
-
|
52
|
-
client.count("com.example.test1", 5)
|
53
|
-
|
54
|
-
sends "com.example.test1:5|c|@0.1" with a sample rate of 10%:
|
17
|
+
We provide a simple commandline wrapper using daemons gem (http://daemons.rubyforge.org/).
|
55
18
|
|
56
|
-
|
19
|
+
Start Server as background daemon:
|
57
20
|
|
58
|
-
|
21
|
+
simple_metrics_server start
|
59
22
|
|
60
|
-
|
23
|
+
Start in foreground:
|
61
24
|
|
62
|
-
|
25
|
+
simple_metrics_server start -t
|
63
26
|
|
64
|
-
|
27
|
+
Show Help:
|
65
28
|
|
66
|
-
|
29
|
+
simple_metrics_server --help
|
67
30
|
|
68
|
-
SimpleMetrics
|
69
|
-
|
31
|
+
SimpleMetrics Web App
|
32
|
+
-----------------
|
70
33
|
|
71
|
-
|
34
|
+
A small Sinatra app is provided using the vegas gem (https://github.com/quirkey/vegas).
|
72
35
|
|
73
|
-
Start
|
36
|
+
Start web app as background daemon:
|
74
37
|
|
75
|
-
|
38
|
+
simple_metrics_web
|
76
39
|
|
77
40
|
Start in foreground:
|
78
41
|
|
79
|
-
|
42
|
+
simple_metrics_web -F
|
80
43
|
|
81
44
|
Show Help:
|
82
45
|
|
83
|
-
|
46
|
+
simple_metrics_web --help
|
84
47
|
|
85
48
|
Round Robin Database Principles in MongoDB
|
86
49
|
------------------------------------------
|
@@ -91,7 +54,7 @@ We use 4 collections in MongoDB each with more coarse timestamp buckets:
|
|
91
54
|
* 10 min
|
92
55
|
* 1 day
|
93
56
|
|
94
|
-
The
|
57
|
+
The 10sec and 1min collections are capped collections and have a fixed size. The others will store the data as long as there is sufficient disc space.
|
95
58
|
|
96
59
|
How can we map these times to graphs?
|
97
60
|
|
data/Rakefile
CHANGED
@@ -2,7 +2,24 @@ require "bundler/gem_tasks"
|
|
2
2
|
require 'rake'
|
3
3
|
require 'rspec/core/rake_task'
|
4
4
|
|
5
|
+
require 'simple_metrics'
|
6
|
+
|
5
7
|
RSpec::Core::RakeTask.new
|
6
8
|
task :default => :spec
|
7
9
|
task :test => :spec
|
8
10
|
|
11
|
+
namespace :simple_metrics do
|
12
|
+
|
13
|
+
desc "Ensure collections and index exist"
|
14
|
+
task :ensure_collections_exist do
|
15
|
+
SimpleMetrics.logger = Logger.new($stdout)
|
16
|
+
SimpleMetrics::DataPointRepository.ensure_collections_exist
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Truncate all collections"
|
20
|
+
task :truncate_collections do
|
21
|
+
SimpleMetrics.logger = Logger.new($stdout)
|
22
|
+
SimpleMetrics::DataPointRepository.truncate_collections
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/bin/populate
CHANGED
@@ -6,20 +6,33 @@ require "bundler/setup"
|
|
6
6
|
require 'optparse'
|
7
7
|
require "simple_metrics"
|
8
8
|
|
9
|
-
|
9
|
+
SimpleMetrics.logger = Logger.new("/dev/null")
|
10
|
+
SimpleMetrics::DataPointRepository.truncate_collections
|
11
|
+
SimpleMetrics::DataPointRepository.ensure_collections_exist
|
10
12
|
|
11
|
-
|
13
|
+
bucket = SimpleMetrics::Bucket.first
|
12
14
|
|
13
|
-
|
15
|
+
name = ENV['NAME'] || "test.page.visits.1"
|
16
|
+
now = Time.now.to_i
|
17
|
+
minute = 60
|
18
|
+
hour = minute * 60
|
14
19
|
|
15
|
-
|
16
|
-
|
20
|
+
def create_dps(name)
|
21
|
+
previous_value = 20
|
22
|
+
(1..1).inject([]) do |result, index|
|
23
|
+
value = previous_value+rand(20)
|
24
|
+
result << SimpleMetrics::DataPoint.create_counter(:name => name, :value => value)
|
25
|
+
previous_value = value
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
17
29
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
30
|
+
counter = 1
|
31
|
+
current = now - 1 * hour
|
32
|
+
while (current < now)
|
33
|
+
dps = create_dps(name)
|
34
|
+
puts "flush data for #{Time.at(current)}, #{counter}"
|
35
|
+
SimpleMetrics::Bucket.flush_data_points(dps, current)
|
36
|
+
current += 10
|
37
|
+
counter += 1
|
38
|
+
end
|
data/bin/simple_metrics_server
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
3
5
|
require "rubygems"
|
4
|
-
require "bundler/setup"
|
5
6
|
require "simple_metrics"
|
6
7
|
require "daemons"
|
7
8
|
|
8
9
|
options = {
|
9
10
|
:backtrace => true,
|
10
11
|
:log_output => true,
|
11
|
-
:dir_mode => :script
|
12
|
+
:dir_mode => :script,
|
12
13
|
}
|
13
14
|
|
14
15
|
Daemons.run_proc("simple_metrics", options) do
|
15
|
-
SimpleMetrics::
|
16
|
+
SimpleMetrics::UDPServer.new.start
|
16
17
|
end
|
17
18
|
|
18
19
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
5
|
+
require "rubygems"
|
6
|
+
require 'vegas'
|
7
|
+
require "simple_metrics"
|
8
|
+
|
9
|
+
Vegas::Runner.new(SimpleMetrics::App, 'simple_metrics_web') do |runner, opts, app|
|
10
|
+
# add more options here
|
11
|
+
end
|
data/config.ru
ADDED
data/default_config.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
server:
|
2
|
+
host: 'localhost'
|
3
|
+
port: 8125
|
4
|
+
flush_interval: 10
|
5
|
+
|
6
|
+
db:
|
7
|
+
host: 'localhost'
|
8
|
+
port: 27017
|
9
|
+
prefix: 'development'
|
10
|
+
options:
|
11
|
+
pool_size: 5
|
12
|
+
timeout: 5
|
13
|
+
|
14
|
+
web:
|
15
|
+
host: 'localhost'
|
16
|
+
port: 5678
|
17
|
+
|
18
|
+
buckets:
|
19
|
+
- name: 'stats_per_10s'
|
20
|
+
seconds: 10
|
21
|
+
capped: true
|
22
|
+
size: 180_000 # 6*30 = 180 (30 min ttl), 180 * 100 = 18.000 (100 data points), 18.000 * 10 (bytes each) = 180.000
|
23
|
+
- name: 'stats_per_1min'
|
24
|
+
seconds: 60
|
25
|
+
capped: true
|
26
|
+
size: 120_000 # 60*3 = 180 (3h ttl), 120 * 100 = 18.000 (100 data points), 18.000 * 10 (bytes each) = 180.000
|
27
|
+
- name: 'stats_per_10min'
|
28
|
+
seconds: 600
|
29
|
+
size: 0
|
30
|
+
capped: false
|
31
|
+
- name: 'stats_per_day'
|
32
|
+
seconds: 86400 # 600*6*24
|
33
|
+
size: 0
|
34
|
+
capped: false
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require "sinatra"
|
2
|
+
require "erubis"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
if defined? Encoding
|
6
|
+
Encoding.default_external = Encoding::UTF_8
|
7
|
+
end
|
8
|
+
|
9
|
+
module SimpleMetrics
|
10
|
+
class App < Sinatra::Base
|
11
|
+
|
12
|
+
set :views, ::File.expand_path('../views', __FILE__)
|
13
|
+
set :public_folder, File.expand_path('../public', __FILE__)
|
14
|
+
|
15
|
+
helpers do
|
16
|
+
def graph_title(time)
|
17
|
+
case time
|
18
|
+
when "minute"
|
19
|
+
"Minute"
|
20
|
+
when "hour"
|
21
|
+
"Hour"
|
22
|
+
when "day"
|
23
|
+
"Day"
|
24
|
+
when "week"
|
25
|
+
"Week"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
get "/api/metrics" do
|
31
|
+
content_type :json
|
32
|
+
metrics = SimpleMetrics::MetricRepository.find_all
|
33
|
+
metrics.inject([]) { |result, m| result << m.attributes }.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
get "/api/metrics/:id" do
|
37
|
+
content_type :json
|
38
|
+
metric = SimpleMetrics::MetricRepository.find_one(to_bson_id(params[:id]))
|
39
|
+
metric.attributes.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
get "/api/graph" do
|
43
|
+
content_type :json
|
44
|
+
from = (params[:from] || Time.now).to_i
|
45
|
+
time = params[:time] || 'minute'
|
46
|
+
targets = params[:targets]
|
47
|
+
data_points = prepare_data_points(from, time, *targets)
|
48
|
+
data_points.to_json
|
49
|
+
end
|
50
|
+
|
51
|
+
get "/*" do
|
52
|
+
erb :index
|
53
|
+
end
|
54
|
+
|
55
|
+
# get "/metrics" do
|
56
|
+
# erb :index
|
57
|
+
# end
|
58
|
+
|
59
|
+
# get "/metrics/:id" do
|
60
|
+
# erb :index
|
61
|
+
# end
|
62
|
+
|
63
|
+
# get "/metric" do
|
64
|
+
# @from = (params[:from] || Time.now).to_i
|
65
|
+
# erb :show
|
66
|
+
# end
|
67
|
+
|
68
|
+
# get "/graph" do
|
69
|
+
# @from = (params[:from] || Time.now).to_i
|
70
|
+
# @time = params[:time] || 'minute'
|
71
|
+
# @targets = params[:target]
|
72
|
+
# @data_points = prepare_data_points(@from, @time, *@targets)
|
73
|
+
# @series = @data_points
|
74
|
+
# erb :graph, :layout => false
|
75
|
+
# end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# params[:id]
|
80
|
+
def to_bson_id(id)
|
81
|
+
BSON::ObjectId.from_string(id)
|
82
|
+
end
|
83
|
+
|
84
|
+
def prepare_data_points(from, time, *targets)
|
85
|
+
to = from - time_range(time)
|
86
|
+
result = SimpleMetrics::Graph.query_all(bucket(time), to, from, *targets)
|
87
|
+
result.map do |data_point|
|
88
|
+
{ :name => data_point.first, :data => data_point.last.map { |p| { :x => p[:ts], :y => p[:value] || 0 } } }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def one_minute
|
93
|
+
60
|
94
|
+
end
|
95
|
+
|
96
|
+
def one_hour
|
97
|
+
one_minute * 60
|
98
|
+
end
|
99
|
+
|
100
|
+
def one_day
|
101
|
+
one_hour * 24
|
102
|
+
end
|
103
|
+
|
104
|
+
def one_week
|
105
|
+
one_day * 7
|
106
|
+
end
|
107
|
+
|
108
|
+
def time_range(time)
|
109
|
+
case time
|
110
|
+
when 'minute'
|
111
|
+
5*one_minute
|
112
|
+
when 'hour'
|
113
|
+
one_hour
|
114
|
+
when 'day'
|
115
|
+
one_day
|
116
|
+
when 'week'
|
117
|
+
one_week
|
118
|
+
else
|
119
|
+
raise "Unknown time param: #{time}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def bucket(time)
|
124
|
+
case time
|
125
|
+
when 'minute'
|
126
|
+
SimpleMetrics::Bucket[0]
|
127
|
+
when 'hour'
|
128
|
+
SimpleMetrics::Bucket[1]
|
129
|
+
when 'day'
|
130
|
+
SimpleMetrics::Bucket[2]
|
131
|
+
when 'week'
|
132
|
+
SimpleMetrics::Bucket[3]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module SimpleMetrics
|
2
|
+
module ArrayAggregation
|
3
|
+
extend self
|
4
|
+
|
5
|
+
class Base
|
6
|
+
def aggregate(dps, name = nil)
|
7
|
+
raise "subclass must implement me!"
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def ts_hash_aggregated(data_points, &block)
|
13
|
+
tmp = {}
|
14
|
+
data_points.each do |dp|
|
15
|
+
if tmp.key?(dp.ts)
|
16
|
+
tmp[dp.ts] = block.call(tmp[dp.ts], dp.value)
|
17
|
+
else
|
18
|
+
tmp[dp.ts] = dp.value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
tmp
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Counter < Base
|
26
|
+
def aggregate(dps, name = nil)
|
27
|
+
tmp_hash = ts_hash_aggregated(dps) do |value1, value2|
|
28
|
+
value1 + value2
|
29
|
+
end
|
30
|
+
|
31
|
+
result = []
|
32
|
+
tmp_hash.each_pair do |key, value|
|
33
|
+
result << SimpleMetrics::DataPoint::Counter.new(:name => name, :ts => key, :value => value)
|
34
|
+
end
|
35
|
+
result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Gauge < Base
|
40
|
+
def aggregate(dps, name = nil)
|
41
|
+
tmp_hash = ts_hash_aggregated(dps) do |value1, value2|
|
42
|
+
(value1 + value2)/2
|
43
|
+
end
|
44
|
+
|
45
|
+
result = []
|
46
|
+
tmp_hash.each_pair do |key, value|
|
47
|
+
result << SimpleMetrics::DataPoint::Gauge.new(:name => name, :ts => key, :value => value)
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Timing < Base
|
54
|
+
end
|
55
|
+
|
56
|
+
class Event < Base
|
57
|
+
end
|
58
|
+
|
59
|
+
def aggregate(dps, name = nil)
|
60
|
+
raise SimpleMetrics::DataPoint::NonMatchingTypesError if has_non_matching_types?(dps)
|
61
|
+
|
62
|
+
dp = dps.first
|
63
|
+
strategy(dp).aggregate(dps, name)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def strategy(dp)
|
69
|
+
if dp.counter?
|
70
|
+
Counter.new
|
71
|
+
elsif dp.gauge?
|
72
|
+
Gauge.new
|
73
|
+
elsif dp.timing?
|
74
|
+
Timing.new
|
75
|
+
elsif dp.event?
|
76
|
+
Event.new
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def has_non_matching_types?(dps)
|
81
|
+
dps.group_by { |dp| dp.type }.size != 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|