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.
Files changed (61) hide show
  1. data/.travis.yml +3 -0
  2. data/README.markdown +16 -53
  3. data/Rakefile +17 -0
  4. data/bin/populate +26 -13
  5. data/bin/simple_metrics_server +4 -3
  6. data/bin/simple_metrics_web +11 -0
  7. data/config.ru +6 -0
  8. data/default_config.yml +34 -0
  9. data/lib/simple_metrics/app.rb +136 -0
  10. data/lib/simple_metrics/array_aggregation.rb +84 -0
  11. data/lib/simple_metrics/bucket.rb +74 -63
  12. data/lib/simple_metrics/configuration.rb +79 -0
  13. data/lib/simple_metrics/data_point/base.rb +59 -0
  14. data/lib/simple_metrics/data_point/counter.rb +13 -0
  15. data/lib/simple_metrics/data_point/event.rb +12 -0
  16. data/lib/simple_metrics/data_point/gauge.rb +13 -0
  17. data/lib/simple_metrics/data_point/timing.rb +12 -0
  18. data/lib/simple_metrics/data_point.rb +32 -136
  19. data/lib/simple_metrics/data_point_repository.rb +131 -0
  20. data/lib/simple_metrics/functions.rb +5 -5
  21. data/lib/simple_metrics/graph.rb +10 -14
  22. data/lib/simple_metrics/metric.rb +28 -0
  23. data/lib/simple_metrics/metric_repository.rb +54 -0
  24. data/lib/simple_metrics/public/css/bootstrap-responsive.min.css +12 -0
  25. data/lib/simple_metrics/public/css/bootstrap.min.css +689 -0
  26. data/lib/simple_metrics/public/css/graph.css +45 -0
  27. data/lib/simple_metrics/public/css/rickshaw.min.css +1 -0
  28. data/lib/simple_metrics/public/img/glyphicons-halflings-white.png +0 -0
  29. data/lib/simple_metrics/public/img/glyphicons-halflings.png +0 -0
  30. data/lib/simple_metrics/public/js/application.js +278 -0
  31. data/lib/simple_metrics/public/js/backbone-0.9.2.min.js +38 -0
  32. data/lib/simple_metrics/public/js/bootstrap.min.js +6 -0
  33. data/lib/simple_metrics/public/js/d3.v2.min.js +4 -0
  34. data/lib/simple_metrics/public/js/handlebars-1.0.0.beta.6.js +1550 -0
  35. data/lib/simple_metrics/public/js/jquery-1.7.1.min.js +4 -0
  36. data/lib/simple_metrics/public/js/rickshaw.min.js +1 -0
  37. data/lib/simple_metrics/public/js/underscore-1.3.1.min.js +31 -0
  38. data/lib/simple_metrics/repository.rb +34 -0
  39. data/lib/simple_metrics/udp_server.rb +81 -0
  40. data/lib/simple_metrics/update_aggregation.rb +62 -0
  41. data/lib/simple_metrics/value_aggregation.rb +63 -0
  42. data/lib/simple_metrics/version.rb +1 -1
  43. data/lib/simple_metrics/views/graph.erb +93 -0
  44. data/lib/simple_metrics/views/index.erb +0 -0
  45. data/lib/simple_metrics/views/layout.erb +119 -0
  46. data/lib/simple_metrics/views/show.erb +31 -0
  47. data/lib/simple_metrics.rb +19 -76
  48. data/simple_metrics.gemspec +6 -0
  49. data/spec/array_aggregation_spec.rb +51 -0
  50. data/spec/bucket_spec.rb +24 -62
  51. data/spec/data_point_repository_spec.rb +114 -0
  52. data/spec/data_point_spec.rb +1 -70
  53. data/spec/graph_spec.rb +2 -20
  54. data/spec/metric_repository_spec.rb +53 -0
  55. data/spec/spec_helper.rb +3 -3
  56. data/spec/value_aggregation_spec.rb +52 -0
  57. metadata +131 -24
  58. data/bin/simple_metrics_client +0 -64
  59. data/lib/simple_metrics/client.rb +0 -83
  60. data/lib/simple_metrics/mongo.rb +0 -48
  61. data/lib/simple_metrics/server.rb +0 -66
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
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 Client
14
+ SimpleMetrics Server
15
15
  --------------------
16
16
 
17
- Commandline client:
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
- client.count("com.example.test1", 5, 0.1)
19
+ Start Server as background daemon:
57
20
 
58
- sends "com.example.test1:5|g" (meaning gauge, an absolute value of 5):
21
+ simple_metrics_server start
59
22
 
60
- client.count("com.example.test1", 5)
23
+ Start in foreground:
61
24
 
62
- sends "com.example.test1:100|ms":
25
+ simple_metrics_server start -t
63
26
 
64
- client.timing("com.example.test1")
27
+ Show Help:
65
28
 
66
- More examples in the examples/ directory.
29
+ simple_metrics_server --help
67
30
 
68
- SimpleMetrics Server
69
- --------------------
31
+ SimpleMetrics Web App
32
+ -----------------
70
33
 
71
- We provide a simple commandline wrapper using daemons gem (http://daemons.rubyforge.org/).
34
+ A small Sinatra app is provided using the vegas gem (https://github.com/quirkey/vegas).
72
35
 
73
- Start Server as background daemond:
36
+ Start web app as background daemon:
74
37
 
75
- simple_metrics_server start
38
+ simple_metrics_web
76
39
 
77
40
  Start in foreground:
78
41
 
79
- simple_metrics_server start -t
42
+ simple_metrics_web -F
80
43
 
81
44
  Show Help:
82
45
 
83
- simple_metrics_server --help
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 10s and 1m collections are capped collections and have a fixed size. The other will store the data as long as we have sufficient disc space.
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
- ts = Time.now.to_i
9
+ SimpleMetrics.logger = Logger.new("/dev/null")
10
+ SimpleMetrics::DataPointRepository.truncate_collections
11
+ SimpleMetrics::DataPointRepository.ensure_collections_exist
10
12
 
11
- name = "test.post.clicks2"
13
+ bucket = SimpleMetrics::Bucket.first
12
14
 
13
- SimpleMetrics::Mongo.ensure_collections_exist
15
+ name = ENV['NAME'] || "test.page.visits.1"
16
+ now = Time.now.to_i
17
+ minute = 60
18
+ hour = minute * 60
14
19
 
15
- SimpleMetrics::Bucket.all.each do |bucket|
16
- ts_bucket = bucket.ts_bucket(ts)
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
- 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
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
@@ -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::Server.new.start
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
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift ::File.expand_path(::File.dirname(__FILE__) + '/lib')
2
+
3
+ require "rubygems"
4
+ require "simple_metrics"
5
+
6
+ run SimpleMetrics::App.new
@@ -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