librato-metrics 0.6.1 → 0.7.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.
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: