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.
Files changed (70) hide show
  1. data/README.markdown +53 -16
  2. data/Rakefile +0 -17
  3. data/bin/populate +13 -26
  4. data/bin/simple_metrics_client +64 -0
  5. data/bin/simple_metrics_server +3 -4
  6. data/lib/simple_metrics.rb +76 -17
  7. data/lib/simple_metrics/bucket.rb +106 -43
  8. data/lib/simple_metrics/client.rb +83 -0
  9. data/lib/simple_metrics/data_point.rb +137 -45
  10. data/lib/simple_metrics/functions.rb +5 -5
  11. data/lib/simple_metrics/graph.rb +32 -69
  12. data/lib/simple_metrics/mongo.rb +48 -0
  13. data/lib/simple_metrics/server.rb +66 -0
  14. data/lib/simple_metrics/version.rb +1 -1
  15. data/simple_metrics.gemspec +0 -6
  16. data/spec/bucket_spec.rb +152 -17
  17. data/spec/data_point_spec.rb +64 -14
  18. data/spec/graph_spec.rb +19 -30
  19. data/spec/spec_helper.rb +3 -3
  20. metadata +24 -139
  21. data/.travis.yml +0 -3
  22. data/bin/simple_metrics_web +0 -11
  23. data/config.ru +0 -6
  24. data/default_config.yml +0 -34
  25. data/lib/simple_metrics/app.rb +0 -52
  26. data/lib/simple_metrics/configuration.rb +0 -97
  27. data/lib/simple_metrics/data_point/base.rb +0 -59
  28. data/lib/simple_metrics/data_point/counter.rb +0 -20
  29. data/lib/simple_metrics/data_point/event.rb +0 -16
  30. data/lib/simple_metrics/data_point/gauge.rb +0 -19
  31. data/lib/simple_metrics/data_point/timing.rb +0 -15
  32. data/lib/simple_metrics/data_point_repository.rb +0 -114
  33. data/lib/simple_metrics/importer.rb +0 -64
  34. data/lib/simple_metrics/metric.rb +0 -29
  35. data/lib/simple_metrics/metric_repository.rb +0 -54
  36. data/lib/simple_metrics/public/css/bootstrap-responsive.min.css +0 -12
  37. data/lib/simple_metrics/public/css/bootstrap.min.css +0 -689
  38. data/lib/simple_metrics/public/css/graph.css +0 -45
  39. data/lib/simple_metrics/public/css/rickshaw.min.css +0 -1
  40. data/lib/simple_metrics/public/img/glyphicons-halflings-white.png +0 -0
  41. data/lib/simple_metrics/public/img/glyphicons-halflings.png +0 -0
  42. data/lib/simple_metrics/public/js/app.js +0 -20
  43. data/lib/simple_metrics/public/js/collections/graph.js +0 -16
  44. data/lib/simple_metrics/public/js/collections/metric.js +0 -9
  45. data/lib/simple_metrics/public/js/helpers.js +0 -23
  46. data/lib/simple_metrics/public/js/lib/backbone-0.9.2.min.js +0 -38
  47. data/lib/simple_metrics/public/js/lib/bootstrap.min.js +0 -6
  48. data/lib/simple_metrics/public/js/lib/d3.v2.min.js +0 -4
  49. data/lib/simple_metrics/public/js/lib/handlebars-1.0.0.beta.6.js +0 -1550
  50. data/lib/simple_metrics/public/js/lib/jquery-1.7.1.min.js +0 -4
  51. data/lib/simple_metrics/public/js/lib/moment.min.js +0 -6
  52. data/lib/simple_metrics/public/js/lib/rickshaw.min.js +0 -1
  53. data/lib/simple_metrics/public/js/lib/underscore-1.3.1.min.js +0 -31
  54. data/lib/simple_metrics/public/js/models/graph.js +0 -6
  55. data/lib/simple_metrics/public/js/models/metric.js +0 -7
  56. data/lib/simple_metrics/public/js/router.js +0 -42
  57. data/lib/simple_metrics/public/js/views/app.js +0 -18
  58. data/lib/simple_metrics/public/js/views/dashboard.js +0 -10
  59. data/lib/simple_metrics/public/js/views/graph.js +0 -101
  60. data/lib/simple_metrics/public/js/views/metric.js +0 -82
  61. data/lib/simple_metrics/public/js/views/metrics.js +0 -10
  62. data/lib/simple_metrics/repository.rb +0 -34
  63. data/lib/simple_metrics/udp_server.rb +0 -81
  64. data/lib/simple_metrics/views/graph.erb +0 -93
  65. data/lib/simple_metrics/views/index.erb +0 -0
  66. data/lib/simple_metrics/views/layout.erb +0 -138
  67. data/lib/simple_metrics/views/show.erb +0 -31
  68. data/spec/data_point_repository_spec.rb +0 -77
  69. data/spec/importer_spec.rb +0 -126
  70. 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
- module DataPoint
3
- extend self
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
- def parse(str)
18
- if str =~ REGEXP
19
- name, value, type, sample_rate = $1, $2, $3, $5
20
- build(:name => name, :value => value, :type => type, :sample_rate => sample_rate)
21
- else
22
- raise ParserError, "Parser Error - Invalid data point: #{str}"
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
- def build(attributes)
27
- case attributes[:type]
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
- end
40
-
41
- def aggregate_values(dps)
42
- raise SimpleMetrics::DataPoint::NonMatchingTypesError if has_non_matching_types?(dps)
43
-
44
- dp = dps.first.dup
45
- dp.value = if dp.counter?
46
- sum(dps)
47
- elsif dp.gauge?
48
- sum(dps) / dps.size
49
- elsif dp.event?
50
- raise "Implement me!"
51
- elsif dp.timing?
52
- raise "Implement me!"
53
- else
54
- raise ArgumentError("Unknown data point type: #{dp}")
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
- dp
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
- private
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 sum(dps)
62
- dps.map { |dp| dp.value }.inject(0) { |result, value| result += value }
148
+ def value
149
+ @value.to_i if @value
63
150
  end
64
151
 
65
- def has_non_matching_types?(dps)
66
- dps.group_by { |dp| dp.type }.size != 1
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
- # calculate the maximum value for multiple targets
6
- #
7
- # params:
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
- def max(*data_points)
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
- def offset(*data_points)
20
+ def offset(*data_points)
21
21
  end
22
22
 
23
23
  # multiple each value
@@ -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; Bucket[0]; end
7
- def hours; Bucket[1]; end
8
- def day; Bucket[2]; end
9
- def week; Bucket[3]; end
12
+ def minutes
13
+ Bucket[0]
14
+ end
10
15
 
11
- def time_range(time)
12
- case time
13
- when 'minute'
14
- 5 * one_minute
15
- when 'hour'
16
- one_hour
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
- results = []
29
+ result = {}
28
30
  Array(targets).each do |target|
29
- results << { :name => target, :data => query(bucket, from, to, target).map { |data| { :x => data.ts, :y => data.value || 0 } } }
31
+ result[target.inspect] = values_only(query(bucket, from, to, target))
30
32
  end
31
- results
33
+ result
32
34
  end
33
35
 
34
36
  def query(bucket, from, to, target)
35
- if wild_card_query?(target)
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 = aggregate(result, target)
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 format: #{target.inspect}"
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 has_non_matching_types?(dps)
71
- dps.group_by { |dp| dp.type }.size != 1
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