simple_metrics 0.3.6 → 0.4.0

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.
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