librato-metrics 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## Changelog
2
2
 
3
+ ### Version 0.7.0
4
+ * Add ability to update metric properties (Christoph Bünte)
5
+ * Add ability to merge queue and aggregator data into a queue
6
+ * Aggregator supports custom source by measurement
7
+ * Add option to clear queued measurements after failed submit
8
+ * Custom user agent support
9
+ * Documentation improvements
10
+
3
11
  ### Version 0.6.1
4
12
  * Loosen restrictions to older versions of faraday and multi_json
5
13
  * Fix symbol casting issue in jruby with metric delete
data/Gemfile CHANGED
@@ -4,4 +4,9 @@ platforms :jruby do
4
4
  gem 'jruby-openssl'
5
5
  end
6
6
 
7
- gemspec
7
+ platforms :ruby do
8
+ # make available for yard under C rubies
9
+ gem 'redcarpet'
10
+ end
11
+
12
+ gemspec
@@ -63,6 +63,7 @@ module Librato
63
63
  extend SingleForwardable
64
64
 
65
65
  TYPES = [:counter, :gauge]
66
+ PLURAL_TYPES = [:counters, :gauges]
66
67
  MIN_MEASURE_TIME = (Time.now-(3600*24*365)).to_i
67
68
 
68
69
  # Expose class methods of Simple via Metrics itself.
@@ -70,7 +71,7 @@ module Librato
70
71
  def_delegators :client, :agent_identifier, :api_endpoint,
71
72
  :api_endpoint=, :authenticate, :connection, :delete,
72
73
  :fetch, :list, :persistence, :persistence=, :persister,
73
- :submit
74
+ :submit, :update
74
75
 
75
76
  # The Librato::Metrics::Client being used by module-level
76
77
  # access.
@@ -5,8 +5,10 @@ module Librato
5
5
  module Metrics
6
6
 
7
7
  class Aggregator
8
- include Processor
8
+ SOURCE_SEPARATOR = '%%' # must not be in valid source name criteria
9
9
 
10
+ include Processor
11
+
10
12
  attr_reader :source
11
13
 
12
14
  def initialize(options={})
@@ -16,14 +18,21 @@ module Librato
16
18
 
17
19
  # Add a metric entry to the metric set:
18
20
  #
19
- # @param Hash metrics metrics to add
20
- # @return Aggregator returns self
21
- def add(args)
22
- args.each do |k, v|
23
- value = v.respond_to?(:each) ? v[:value] : v
21
+ # @param [Hash] measurements measurements to add
22
+ # @return [Aggregator] returns self
23
+ def add(measurements)
24
+ measurements.each do |metric, data|
25
+ if data.respond_to?(:each) # hash form
26
+ value = data[:value]
27
+ if data[:source]
28
+ metric = "#{metric}#{SOURCE_SEPARATOR}#{data[:source]}"
29
+ end
30
+ else
31
+ value = data
32
+ end
24
33
 
25
- @aggregated[k] ||= Aggregate.new
26
- @aggregated[k] << value
34
+ @aggregated[metric] ||= Aggregate.new
35
+ @aggregated[metric] << value
27
36
  end
28
37
  autosubmit_check
29
38
  self
@@ -38,25 +47,32 @@ module Librato
38
47
 
39
48
  # Remove all queued metrics
40
49
  #
41
- def flush
50
+ def clear
42
51
  @aggregated = {}
43
52
  end
44
- alias :clear :flush
53
+ alias :flush :clear
45
54
 
46
55
  def queued
47
56
  gauges = []
48
57
 
49
- @aggregated.each do |k,v|
50
- gauges << {
51
- :name => k.to_s,
52
- :count => v.count,
53
- :sum => v.sum,
58
+ @aggregated.each do |metric, data|
59
+ source = nil
60
+ metric = metric.to_s
61
+ if metric.include?(SOURCE_SEPARATOR)
62
+ metric, source = metric.split(SOURCE_SEPARATOR)
63
+ end
64
+ entry = {
65
+ :name => metric,
66
+ :count => data.count,
67
+ :sum => data.sum,
54
68
 
55
69
  # TODO: make float/non-float consistent in the gem
56
- :min => v.min.to_f,
57
- :max => v.max.to_f
70
+ :min => data.min.to_f,
71
+ :max => data.max.to_f
58
72
  # TODO: expose v.sum2 and include
59
73
  }
74
+ entry[:source] = source if source
75
+ gauges << entry
60
76
  end
61
77
 
62
78
  req = { :gauges => gauges }
@@ -60,6 +60,19 @@ module Librato
60
60
  @connection ||= Connection.new(:client => self, :api_endpoint => api_endpoint)
61
61
  end
62
62
 
63
+ # Overrride user agent for this client's connections. If you
64
+ # are trying to specify an agent identifier for developer
65
+ # program, see #agent_identifier.
66
+ #
67
+ def custom_user_agent=(agent)
68
+ @user_agent = agent
69
+ @connection = nil
70
+ end
71
+
72
+ def custom_user_agent
73
+ @user_agent
74
+ end
75
+
63
76
  # Completely delete metrics with the given names. Be
64
77
  # careful with this, this is instant and permanent.
65
78
  #
@@ -91,7 +104,7 @@ module Librato
91
104
  #
92
105
  # @example Get 20 most recent data points for a specific source
93
106
  # data = Librato::Metrics.fetch :temperature, :count => 20,
94
- # :source => 'app1'
107
+ # :source => 'app1'
95
108
  #
96
109
  # @example Get the 20 most recent 15 minute data point rollups
97
110
  # data = Librato::Metrics.fetch :temperature, :count => 20,
@@ -172,7 +185,7 @@ module Librato
172
185
 
173
186
  # Set persistence type to use when saving metrics.
174
187
  #
175
- # @param [Symbol] persistence_type
188
+ # @param [Symbol] persist_method
176
189
  def persistence=(persist_method)
177
190
  @persistence = persist_method
178
191
  end
@@ -185,10 +198,27 @@ module Librato
185
198
  # Submit all queued metrics.
186
199
  #
187
200
  def submit(args)
188
- @queue ||= Queue.new(:client => self, :skip_measurement_times => true)
201
+ @queue ||= Queue.new(:client => self,
202
+ :skip_measurement_times => true,
203
+ :clear_failures => true)
189
204
  @queue.add args
190
205
  @queue.submit
191
206
  end
207
+
208
+ # Update metric with the given name.
209
+ #
210
+ # @example Update metric 'temperature'
211
+ # Librato::Metrics.update :temperature, :period => 15, :attributes => { :color => 'F00' }
212
+ #
213
+ # @example Update metric 'humidity', creating it if it doesn't exist
214
+ # Librato::Metrics.update 'humidity', :type => :gauge, :period => 60, :display_name => 'Humidity'
215
+ #
216
+ def update(metric, options = {})
217
+ connection.put do |request|
218
+ request.url connection.build_url("metrics/#{metric}")
219
+ request.body = MultiJson.dump(options)
220
+ end
221
+ end
192
222
 
193
223
  private
194
224
 
@@ -48,6 +48,7 @@ module Librato
48
48
  # User-agent used when making requests.
49
49
  #
50
50
  def user_agent
51
+ return @client.custom_user_agent if @client.custom_user_agent
51
52
  ua_chunks = []
52
53
  agent_identifier = @client.agent_identifier
53
54
  if agent_identifier && !agent_identifier.empty?
@@ -9,6 +9,7 @@ module Librato
9
9
  class NoMetricsProvided < MetricsError; end
10
10
  class NoClientProvided < MetricsError; end
11
11
  class InvalidMeasureTime < MetricsError; end
12
+ class NotMergeable < MetricsError; end
12
13
 
13
14
  class NetworkError < StandardError; end
14
15
 
@@ -30,16 +30,20 @@ module Librato
30
30
  options = {:per_request => @per_request}
31
31
  if persister.persist(self.client, self.queued, options)
32
32
  @last_submit_time = Time.now
33
- flush and return true
33
+ clear and return true
34
34
  end
35
35
  false
36
+ rescue ClientError
37
+ # clean up if we hit exceptions if asked to
38
+ clear if @clear_on_failure
39
+ raise
36
40
  end
37
41
 
38
42
  # Capture execution time for a block and queue
39
43
  # it as the value for a metric. Times are recorded
40
44
  # in milliseconds.
41
45
  #
42
- # Options are the same as for {#add}.
46
+ # Options are the same as for #add.
43
47
  #
44
48
  # @example Queue API request response time
45
49
  # queue.time :api_request_time do
@@ -79,6 +83,7 @@ module Librato
79
83
  @per_request = options[:per_request] || MEASUREMENTS_PER_REQUEST
80
84
  @source = options[:source]
81
85
  @create_time = Time.now
86
+ @clear_on_failure = options[:clear_failures] || false
82
87
  end
83
88
 
84
89
  def autosubmit_check
@@ -16,10 +16,10 @@ module Librato
16
16
 
17
17
  # Add a metric entry to the metric set:
18
18
  #
19
- # @param Hash metrics metrics to add
20
- # @return Queue returns self
21
- def add(args)
22
- args.each do |key, value|
19
+ # @param [Hash] measurements measurements to add
20
+ # @return [Queue] returns self
21
+ def add(measurements)
22
+ measurements.each do |key, value|
23
23
  if value.respond_to?(:each)
24
24
  metric = value
25
25
  metric[:name] = key.to_s
@@ -57,11 +57,10 @@ module Librato
57
57
 
58
58
  # Remove all queued metrics
59
59
  #
60
- def flush
60
+ def clear
61
61
  @queued = {}
62
62
  end
63
- alias :clear :flush
64
- alias :flush_queued :flush
63
+ alias :flush :clear
65
64
 
66
65
  # Currently queued gauges
67
66
  #
@@ -69,6 +68,35 @@ module Librato
69
68
  def gauges
70
69
  @queued[:gauges] || []
71
70
  end
71
+
72
+ # Combines queueable measures from the given object
73
+ # into this queue.
74
+ #
75
+ # @example Merging queues for more performant submission
76
+ # queue1.merge!(queue2)
77
+ # queue1.submit # submits combined contents
78
+ #
79
+ # @return self
80
+ def merge!(mergeable)
81
+ if mergeable.respond_to?(:queued)
82
+ to_merge = mergeable.queued
83
+ elsif mergeable.respond_to?(:has_key?)
84
+ to_merge = mergeable
85
+ else
86
+ raise NotMergeable
87
+ end
88
+ Metrics::PLURAL_TYPES.each do |type|
89
+ if to_merge[type]
90
+ measurements = reconcile_source(to_merge[type], to_merge[:source])
91
+ if @queued[type]
92
+ @queued[type] += measurements
93
+ else
94
+ @queued[type] = measurements
95
+ end
96
+ end
97
+ end
98
+ self
99
+ end
72
100
 
73
101
  # All currently queued metrics
74
102
  #
@@ -96,6 +124,17 @@ module Librato
96
124
  end
97
125
  end
98
126
 
127
+ def reconcile_source(measurements, source)
128
+ return measurements if !source || source == @source
129
+ measurements.map! do |measurement|
130
+ unless measurement[:source]
131
+ measurement[:source] = source
132
+ end
133
+ measurement
134
+ end
135
+ measurements
136
+ end
137
+
99
138
  def submit_check
100
139
  autosubmit_check # in Processor
101
140
  if @autosubmit_count && self.length >= @autosubmit_count
@@ -1,5 +1,5 @@
1
1
  module Librato
2
2
  module Metrics
3
- VERSION = "0.6.1"
3
+ VERSION = "0.7.0"
4
4
  end
5
5
  end
@@ -150,8 +150,69 @@ module Librato
150
150
  data['baz'][0]['value'] == 456.0
151
151
  end
152
152
  end
153
+
154
+ it "should not retain errors" do
155
+ delete_all_metrics
156
+ Metrics.submit :foo => {:type => :counter, :value => 12}
157
+ lambda {
158
+ Metrics.submit :foo => 15 # submitting as gauge
159
+ }.should raise_error
160
+ lambda {
161
+ Metrics.submit :foo => {:type => :counter, :value => 17}
162
+ }.should_not raise_error
163
+ end
153
164
 
154
165
  end
166
+
167
+ describe "#update" do
168
+
169
+ context "with existing metric" do
170
+ before do
171
+ delete_all_metrics
172
+ Metrics.submit :foo => 123
173
+ end
174
+
175
+ it "should upate the metric" do
176
+ Metrics.update :foo, :display_name => "Foo Metric",
177
+ :period => 15,
178
+ :attributes => {
179
+ :display_max => 1000
180
+ }
181
+ foo = Metrics.fetch :foo
182
+ foo['display_name'].should == 'Foo Metric'
183
+ foo['period'].should == 15
184
+ foo['attributes'].should == {'display_max' => 1000}
185
+ end
186
+ end
187
+
188
+ context "without an existing metric" do
189
+ it "should create the metric if type specified" do
190
+ delete_all_metrics
191
+ Metrics.update :foo, :display_name => "Foo Metric",
192
+ :type => :gauge,
193
+ :period => 15,
194
+ :attributes => {
195
+ :display_max => 1000
196
+ }
197
+ foo = Metrics.fetch :foo
198
+ foo['display_name'].should == 'Foo Metric'
199
+ foo['period'].should == 15
200
+ foo['attributes'].should == {'display_max' => 1000}
201
+ end
202
+
203
+ it "should raise error if no type specified" do
204
+ delete_all_metrics
205
+ lambda {
206
+ Metrics.update :foo, :display_name => "Foo Metric",
207
+ :period => 15,
208
+ :attributes => {
209
+ :display_max => 1000
210
+ }
211
+ }.should raise_error
212
+ end
213
+ end
214
+
215
+ end
155
216
 
156
217
  end
157
218
  end
data/spec/spec_helper.rb CHANGED
@@ -70,9 +70,13 @@ end
70
70
  # @example
71
71
  # {:foo => [1,3,2]}.should equal_unordered({:foo => [1,2,3]})
72
72
  RSpec::Matchers.define :equal_unordered do |result|
73
- result.each { |key, value| result[key] = value.to_set }
73
+ result.each do |key, value|
74
+ result[key] = value.to_set if value.respond_to?(:to_set)
75
+ end
74
76
  match do |target|
75
- target.each { |key, value| target[key] = value.to_set }
77
+ target.each do |key, value|
78
+ target[key] = value.to_set if value.respond_to?(:to_set)
79
+ end
76
80
  target == result
77
81
  end
78
82
  end
@@ -76,6 +76,20 @@ module Librato
76
76
  }
77
77
  subject.queued.should equal_unordered(expected)
78
78
  end
79
+
80
+ it "should respect source argument" do
81
+ subject.add :foo => {:source => 'alpha', :value => 1}
82
+ subject.add :foo => 5
83
+ subject.add :foo => {:source => :alpha, :value => 6}
84
+ subject.add :foo => 10
85
+ expected = { :gauges => [
86
+ { :name => 'foo', :source => 'alpha', :count => 2,
87
+ :sum => 7.0, :min => 1.0, :max => 6.0 },
88
+ { :name => 'foo', :count => 2,
89
+ :sum => 15.0, :min => 5.0, :max => 10.0 }
90
+ ]}
91
+ subject.queued.should equal_unordered(expected)
92
+ end
79
93
  end
80
94
 
81
95
  context "with multiple hash arguments" do
@@ -37,6 +37,15 @@ module Librato
37
37
  end
38
38
  end
39
39
 
40
+ context "with a custom user agent set" do
41
+ it "should use custom user agent" do
42
+ client = Client.new
43
+ client.custom_user_agent = 'foo agent'
44
+ connection = Connection.new(:client => client)
45
+ connection.user_agent.should == 'foo agent'
46
+ end
47
+ end
48
+
40
49
  # TODO: verify user agent is being sent with rackup test
41
50
  end
42
51
 
@@ -163,6 +163,101 @@ module Librato
163
163
  end
164
164
  end
165
165
 
166
+ describe "#merge!" do
167
+ context "with another queue" do
168
+ it "should merge gauges" do
169
+ q1 = Queue.new
170
+ q1.add :foo => 123, :bar => 456
171
+ q2 = Queue.new
172
+ q2.add :baz => 678
173
+ q2.merge!(q1)
174
+ expected = {:gauges=>[{:name=>"foo", :value=>123, :measure_time => @time},
175
+ {:name=>"bar", :value=>456, :measure_time => @time},
176
+ {:name=>"baz", :value=>678, :measure_time => @time}]}
177
+ q2.queued.should equal_unordered(expected)
178
+ end
179
+
180
+ it "should merge counters" do
181
+ q1 = Queue.new
182
+ q1.add :users => {:type => :counter, :value => 1000}
183
+ q1.add :sales => {:type => :counter, :value => 250}
184
+ q2 = Queue.new
185
+ q2.add :signups => {:type => :counter, :value => 500}
186
+ q2.merge!(q1)
187
+ expected = {:counters=>[{:name=>"users", :value=>1000, :measure_time => @time},
188
+ {:name=>"sales", :value=>250, :measure_time => @time},
189
+ {:name=>"signups", :value=>500, :measure_time => @time}]}
190
+ q2.queued.should equal_unordered(expected)
191
+ end
192
+
193
+ it "should maintain specified sources" do
194
+ q1 = Queue.new
195
+ q1.add :neo => {:source => 'matrix', :value => 123}
196
+ q2 = Queue.new(:source => 'red_pill')
197
+ q2.merge!(q1)
198
+ q2.queued[:gauges][0][:source].should == 'matrix'
199
+ end
200
+
201
+ it "should not change default source" do
202
+ q1 = Queue.new(:source => 'matrix')
203
+ q1.add :neo => 456
204
+ q2 = Queue.new(:source => 'red_pill')
205
+ q2.merge!(q1)
206
+ q2.queued[:source].should == 'red_pill'
207
+ end
208
+
209
+ it "should track previous default source" do
210
+ q1 = Queue.new(:source => 'matrix')
211
+ q1.add :neo => 456
212
+ q2 = Queue.new(:source => 'red_pill')
213
+ q2.add :morpheus => 678
214
+ q2.merge!(q1)
215
+ q2.queued[:gauges].each do |gauge|
216
+ if gauge[:name] == 'neo'
217
+ gauge[:source].should == 'matrix'
218
+ end
219
+ end
220
+ end
221
+
222
+ it "should handle empty cases" do
223
+ q1 = Queue.new
224
+ q1.add :foo => 123, :users => {:type => :counter, :value => 1000}
225
+ q2 = Queue.new
226
+ q2.merge!(q1)
227
+ expected = {:counters => [{:name=>"users", :value=>1000, :measure_time => @time}],
228
+ :gauges => [{:name=>"foo", :value=>123, :measure_time => @time}]}
229
+ q2.queued.should == expected
230
+ end
231
+ end
232
+
233
+ context "with an aggregator" do
234
+ it "should merge" do
235
+ aggregator = Aggregator.new(:source => 'aggregator')
236
+ aggregator.add :timing => 102
237
+ aggregator.add :timing => 203
238
+ queue = Queue.new(:source => 'queue')
239
+ queue.add :gauge => 42
240
+ queue.merge!(aggregator)
241
+ expected = {:gauges=>[{:name=>"gauge", :value=>42, :measure_time=>@time},
242
+ {:name=>"timing", :count=>2, :sum=>305.0, :min=>102.0, :max=>203.0, :source=>"aggregator"}],
243
+ :source=>'queue'}
244
+ queue.queued.should equal_unordered(expected)
245
+
246
+ end
247
+ end
248
+
249
+ context "with a hash" do
250
+ it "should merge" do
251
+ to_merge = {:gauges=>[{:name => 'foo', :value => 123}],
252
+ :counters=>[{:name => 'bar', :value => 456}]}
253
+ q = Queue.new
254
+ q.merge!(to_merge)
255
+ q.gauges.length.should == 1
256
+ q.counters.length.should == 1
257
+ end
258
+ end
259
+ end
260
+
166
261
  describe "#per_request" do
167
262
  it "should default to 500" do
168
263
  subject.per_request.should == 500
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: librato-metrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-11 00:00:00.000000000 Z
12
+ date: 2012-07-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday
@@ -163,6 +163,7 @@ extra_rdoc_files:
163
163
  - LICENSE
164
164
  files:
165
165
  - .gitignore
166
+ - .rspec
166
167
  - .travis.yml
167
168
  - CHANGELOG.md
168
169
  - Gemfile
@@ -214,7 +215,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
214
215
  version: '0'
215
216
  segments:
216
217
  - 0
217
- hash: -2003019020857888110
218
+ hash: -1999246465148166572
218
219
  required_rubygems_version: !ruby/object:Gem::Requirement
219
220
  none: false
220
221
  requirements: