simple_metrics 0.2.3 → 0.3.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/.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
|