librato-metrics 0.2.3 → 0.3.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/.gitignore CHANGED
@@ -17,3 +17,5 @@ doc
17
17
 
18
18
  # rcov generated
19
19
  coverage
20
+
21
+ test_env.sh
data/README.md CHANGED
@@ -12,6 +12,12 @@ In your shell:
12
12
  Then, in your application or script:
13
13
 
14
14
  require 'librato/metrics'
15
+
16
+ ### Optional steps
17
+
18
+ For best performance we recommend installing [yajl-ruby](https://github.com/brianmario/yajl-ruby):
19
+
20
+ gem install yajl-ruby
15
21
 
16
22
  ## Quick Start
17
23
 
@@ -101,6 +107,10 @@ Get the 20 most recent 15 minute data point rollups for `temperature`:
101
107
 
102
108
  There are many more options supported for querying, take a look at the [REST API docs](http://dev.librato.com/v1/get/gauges/:name) or the [fetch documentation](http://rubydoc.info/github/librato/librato-metrics/master/Librato/Metrics.fetch) for more details.
103
109
 
110
+ ## Thread Safety
111
+
112
+ The `librato-metrics` gem currently does not do internal locking for thread safety. When used in multi-threaded applications, please add your own mutexes for sensitive operations.
113
+
104
114
  ## Known Issues & Coming Improvements
105
115
 
106
116
  This is an early release and as such is lacking some capabilities slated for future releases.
data/Rakefile CHANGED
@@ -5,8 +5,27 @@ Bundler::GemHelper.install_tasks
5
5
 
6
6
  # Testing
7
7
  require 'rspec/core/rake_task'
8
- RSpec::Core::RakeTask.new(:spec) do |t|
9
- t.rspec_opts = '--color'
8
+
9
+ desc "Run all tests"
10
+ task :spec do
11
+ Rake::Task['spec:unit'].execute
12
+ if ENV['TEST_API_USER'] && ENV['TEST_API_KEY']
13
+ Rake::Task['spec:integration'].execute
14
+ else
15
+ puts "TEST_API_USER and TEST_API_KEY not in environment, skipping integration tests..."
16
+ end
17
+ end
18
+
19
+ namespace :spec do
20
+ RSpec::Core::RakeTask.new(:unit) do |t|
21
+ t.rspec_opts = '--color'
22
+ t.pattern = 'spec/unit/**/*_spec.rb'
23
+ end
24
+
25
+ RSpec::Core::RakeTask.new(:integration) do |t|
26
+ t.rspec_opts = '--color'
27
+ t.pattern = 'spec/integration/**/*_spec.rb'
28
+ end
10
29
  end
11
30
 
12
31
  task :default => :spec
@@ -3,13 +3,14 @@ $:.unshift(File.dirname(__FILE__)) unless
3
3
 
4
4
  require 'base64'
5
5
  require 'excon'
6
- require 'json'
6
+ require 'multi_json'
7
7
 
8
8
  require 'metrics/errors'
9
9
  require 'metrics/persistence'
10
10
  require 'metrics/queue'
11
11
  require 'metrics/simple'
12
12
  require 'metrics/version'
13
+ require 'metrics/collect'
13
14
 
14
15
  module Librato
15
16
 
@@ -59,8 +60,6 @@ module Librato
59
60
  # @param [Symbol|String] metric Metric name
60
61
  # @param [Hash] options Query options
61
62
  def self.fetch(metric, options={})
62
- # TODO: look up type when not specified.
63
- type = options.delete(:type) || 'gauge'
64
63
  query = options.dup
65
64
  if query[:start_time].respond_to?(:year)
66
65
  query[:start_time] = query[:start_time].to_i
@@ -71,9 +70,9 @@ module Librato
71
70
  unless query.empty?
72
71
  query[:resolution] ||= 1
73
72
  end
74
- response = connection.get(:path => "v1/#{type}s/#{metric}.json",
73
+ response = connection.get(:path => "v1/metrics/#{metric}",
75
74
  :query => query, :expects => 200)
76
- parsed = JSON.parse(response.body)
75
+ parsed = MultiJson.decode(response.body)
77
76
  # TODO: pagination support
78
77
  query.empty? ? parsed : parsed["measurements"]
79
78
  end
@@ -90,10 +89,9 @@ module Librato
90
89
  def self.list(options={})
91
90
  query = {}
92
91
  query[:name] = options[:name] if options[:name]
93
- response = connection.get(:path => 'v1/metrics.json',
94
- :query => query, :expects => 200)
95
- # TODO: pagination support
96
- JSON.parse(response.body)["metrics"]
92
+ offset = 0
93
+ path = "v1/metrics"
94
+ Collect.paginated_metrics connection, path, query
97
95
  end
98
96
 
99
97
  end
@@ -0,0 +1,32 @@
1
+ module Librato
2
+ module Metrics
3
+ class Collect
4
+
5
+ MAX_RESULTS = 100
6
+
7
+ # Aggregates all results of paginated elements, requesting more collections as needed
8
+ #
9
+ # @param [Excon] connection Connection to Metrics service
10
+ # @param [String] path API uri
11
+ # @param [Hash] query Query options
12
+ def self.paginated_metrics connection, path, query
13
+ results = []
14
+ response = connection.get(:path => path,
15
+ :query => query, :expects => 200)
16
+ parsed = MultiJson.decode(response.body)
17
+ results = parsed["metrics"]
18
+ return results if parsed["query"]["found"] <= MAX_RESULTS
19
+ query[:offset] = MAX_RESULTS
20
+ begin
21
+ response = connection.get(:path => path,
22
+ :query => query, :expects => 200)
23
+ parsed = MultiJson.decode(response.body)
24
+ results.push(*parsed["metrics"])
25
+ query[:offset] += MAX_RESULTS
26
+ end while query[:offset] < parsed["query"]["found"]
27
+ results
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -10,8 +10,8 @@ module Librato
10
10
  # Metrics web API.
11
11
  #
12
12
  def persist(queued)
13
- payload = queued.to_json
14
- Simple.connection.post(:path => '/v1/metrics.json',
13
+ payload = MultiJson.encode(queued)
14
+ Simple.connection.post(:path => '/v1/metrics',
15
15
  :headers => {'Content-Type' => 'application/json'},
16
16
  :body => payload, :expects => 200)
17
17
  end
@@ -40,11 +40,19 @@ module Librato
40
40
  @queued[:counters] || []
41
41
  end
42
42
 
43
+ # Are any metrics currently queued?
44
+ #
45
+ # @return Boolean
46
+ def empty?
47
+ @queued.empty?
48
+ end
49
+
43
50
  # Remove all queued metrics
44
51
  #
45
52
  def flush
46
53
  @queued = {}
47
54
  end
55
+ alias :clear :flush
48
56
  alias :flush_queued :flush
49
57
 
50
58
  # The object this MetricSet will use to persist
@@ -67,6 +75,14 @@ module Librato
67
75
  @queued
68
76
  end
69
77
 
78
+ # Count of metrics currently queued
79
+ #
80
+ # @return Integer
81
+ def size
82
+ self.queued.inject(0) { |result, data| result + data.last.size }
83
+ end
84
+ alias :length :size
85
+
70
86
  # Persist currently queued metrics
71
87
  #
72
88
  # @return Boolean
@@ -1,5 +1,5 @@
1
1
  module Librato
2
2
  module Metrics
3
- VERSION = "0.2.3"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
25
25
 
26
26
  ## runtime dependencies
27
27
  s.add_dependency 'excon', '~>0.7.12'
28
+ s.add_dependency 'multi_json'
28
29
 
29
30
  ## development dependencies
30
31
  s.add_development_dependency 'rake'
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ module Librato
4
+ describe Metrics do
5
+ before(:all) { prep_integration_tests }
6
+
7
+ describe "#fetch" do
8
+ before(:all) do
9
+ delete_all_metrics
10
+ Metrics.submit :my_counter => {:type => :counter, :value => 0}
11
+ 1.upto(2).each do |i|
12
+ sleep 1
13
+ Metrics.submit :my_counter => {:type => :counter, :value => i}
14
+ Metrics.submit :my_counter => {:source => 'baz', :type => :counter, :value => i+1}
15
+ end
16
+ end
17
+
18
+ context "without arguments" do
19
+ it "should get metric attributes" do
20
+ metric = Metrics.fetch :my_counter
21
+ metric['name'].should == 'my_counter'
22
+ metric['type'].should == 'counter'
23
+ end
24
+ end
25
+
26
+ context "with a start_time" do
27
+ it "should return entries since that time" do
28
+ data = Metrics.fetch :my_counter, :start_time => Time.now-3600 # 1 hr ago
29
+ data['unassigned'].length.should == 3
30
+ data['baz'].length.should == 2
31
+ end
32
+ end
33
+
34
+ context "with a count limit" do
35
+ it "should return that number of entries per source" do
36
+ data = Metrics.fetch :my_counter, :count => 2
37
+ data['unassigned'].length.should == 2
38
+ data['baz'].length.should == 2
39
+ end
40
+ end
41
+
42
+ context "with a source limit" do
43
+ it "should only return that source" do
44
+ data = Metrics.fetch :my_counter, :source => 'baz', :start_time => Time.now-3600
45
+ data['baz'].length.should == 2
46
+ data['unassigned'].should be_nil
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ describe "#list" do
53
+ before(:all) do
54
+ delete_all_metrics
55
+ Metrics.submit :foo => 123, :bar => 345, :baz => 678, :foo_2 => 901
56
+ end
57
+
58
+ context "without arguments" do
59
+ it "should list all metrics" do
60
+ metric_names = Metrics.list.map { |metric| metric['name'] }
61
+ metric_names.sort.should == %w{foo bar baz foo_2}.sort
62
+ end
63
+ end
64
+
65
+ context "with a name argument" do
66
+ it "should list metrics that match" do
67
+ metric_names = Metrics.list(:name => 'foo').map { |metric| metric['name'] }
68
+ metric_names.sort.should == %w{foo foo_2}.sort
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ describe "#submit" do
75
+
76
+ context "with a gauge" do
77
+ before(:all) do
78
+ delete_all_metrics
79
+ Metrics.submit :foo => 123
80
+ end
81
+
82
+ it "should create the metrics" do
83
+ metric = Metrics.list[0]
84
+ metric['name'].should == 'foo'
85
+ metric['type'].should == 'gauge'
86
+ end
87
+
88
+ it "should store their data" do
89
+ data = Metrics.fetch :foo, :count => 1
90
+ data.should_not be_empty
91
+ data['unassigned'][0]['value'] == 123.0
92
+ end
93
+ end
94
+
95
+ context "with a counter" do
96
+ before(:all) do
97
+ delete_all_metrics
98
+ Metrics.submit :bar => {:type => :counter, :source => 'baz', :value => 456}
99
+ end
100
+
101
+ it "should create the metrics" do
102
+ metric = Metrics.list[0]
103
+ metric['name'].should == 'bar'
104
+ metric['type'].should == 'counter'
105
+ end
106
+
107
+ it "should store their data" do
108
+ data = Metrics.fetch :bar, :count => 1
109
+ data.should_not be_empty
110
+ data['baz'][0]['value'] == 456.0
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+ end
data/spec/spec_helper.rb CHANGED
@@ -7,4 +7,23 @@ require 'librato/metrics'
7
7
 
8
8
  RSpec.configure do |config|
9
9
 
10
+ # set up test account credentials for integration tests
11
+ def prep_integration_tests
12
+ raise 'no TEST_API_USER specified in environment' unless ENV['TEST_API_USER']
13
+ raise 'no TEST_API_KEY specified in environment' unless ENV['TEST_API_KEY']
14
+ if ENV['TEST_API_ENDPOINT']
15
+ Librato::Metrics.api_endpoint = ENV['TEST_API_ENDPOINT']
16
+ end
17
+ Librato::Metrics.authenticate ENV['TEST_API_USER'], ENV['TEST_API_KEY']
18
+ end
19
+
20
+ # purge all metrics from test account
21
+ def delete_all_metrics
22
+ connection = Librato::Metrics.connection
23
+ Librato::Metrics.list.each do |metric|
24
+ #puts "deleting #{metric['name']}..."
25
+ connection.delete(:path => "v1/metrics/#{metric['name']}", :expects => 204)
26
+ end
27
+ end
28
+
10
29
  end
@@ -74,6 +74,17 @@ module Librato
74
74
  end
75
75
  end
76
76
 
77
+ describe "#empty?" do
78
+ it "should return true when nothing queued" do
79
+ subject.empty?.should be_true
80
+ end
81
+
82
+ it "should return false with queued items" do
83
+ subject.add :foo => {:type => :gauge, :value => 121212}
84
+ subject.empty?.should be_false
85
+ end
86
+ end
87
+
77
88
  describe "#gauges" do
78
89
  it "should return currently queued gauges" do
79
90
  subject.add :transactions => {:type => :counter, :value => 12345},
@@ -86,6 +97,19 @@ module Librato
86
97
  end
87
98
  end
88
99
 
100
+ describe "#size" do
101
+ it "should return empty if gauges and counters are emtpy" do
102
+ subject.size.should eq 0
103
+ end
104
+ it "should return count of gauges and counters if added" do
105
+ subject.add :transactions => {:type => :counter, :value => 12345},
106
+ :register_cents => {:type => :gauge, :value => 211101}
107
+ subject.add :transactions => {:type => :counter, :value => 12345},
108
+ :register_cents => {:type => :gauge, :value => 211101}
109
+ subject.size.should eql 4
110
+ end
111
+ end
112
+
89
113
  describe "#submit" do
90
114
  before(:all) do
91
115
  Librato::Metrics.authenticate 'me@librato.com', 'foo'
@@ -16,22 +16,6 @@ module Librato
16
16
 
17
17
  end
18
18
 
19
- describe "#list" do
20
-
21
- context "without arguments" do
22
-
23
- it "should list all metrics"
24
-
25
- end
26
-
27
- context "with a name argument" do
28
-
29
- it "should list metrics that match"
30
-
31
- end
32
-
33
- end
34
-
35
19
  describe "#persistence" do
36
20
 
37
21
  it "should allow configuration of persistence method" do
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.2.3
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-02 00:00:00.000000000Z
12
+ date: 2012-02-15 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: excon
16
- requirement: &70249860959660 !ruby/object:Gem::Requirement
16
+ requirement: &70282326461040 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,21 @@ dependencies:
21
21
  version: 0.7.12
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70249860959660
24
+ version_requirements: *70282326461040
25
+ - !ruby/object:Gem::Dependency
26
+ name: multi_json
27
+ requirement: &70282326460600 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70282326460600
25
36
  - !ruby/object:Gem::Dependency
26
37
  name: rake
27
- requirement: &70249860958680 !ruby/object:Gem::Requirement
38
+ requirement: &70282326460120 !ruby/object:Gem::Requirement
28
39
  none: false
29
40
  requirements:
30
41
  - - ! '>='
@@ -32,10 +43,10 @@ dependencies:
32
43
  version: '0'
33
44
  type: :development
34
45
  prerelease: false
35
- version_requirements: *70249860958680
46
+ version_requirements: *70282326460120
36
47
  - !ruby/object:Gem::Dependency
37
48
  name: rspec
38
- requirement: &70249860956780 !ruby/object:Gem::Requirement
49
+ requirement: &70282326459480 !ruby/object:Gem::Requirement
39
50
  none: false
40
51
  requirements:
41
52
  - - ~>
@@ -43,10 +54,10 @@ dependencies:
43
54
  version: 2.6.0
44
55
  type: :development
45
56
  prerelease: false
46
- version_requirements: *70249860956780
57
+ version_requirements: *70282326459480
47
58
  - !ruby/object:Gem::Dependency
48
59
  name: yard
49
- requirement: &70249860955580 !ruby/object:Gem::Requirement
60
+ requirement: &70282326458940 !ruby/object:Gem::Requirement
50
61
  none: false
51
62
  requirements:
52
63
  - - ! '>='
@@ -54,10 +65,10 @@ dependencies:
54
65
  version: '0'
55
66
  type: :development
56
67
  prerelease: false
57
- version_requirements: *70249860955580
68
+ version_requirements: *70282326458940
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: rdiscount
60
- requirement: &70249860954580 !ruby/object:Gem::Requirement
71
+ requirement: &70282326458280 !ruby/object:Gem::Requirement
61
72
  none: false
62
73
  requirements:
63
74
  - - ! '>='
@@ -65,7 +76,7 @@ dependencies:
65
76
  version: '0'
66
77
  type: :development
67
78
  prerelease: false
68
- version_requirements: *70249860954580
79
+ version_requirements: *70282326458280
69
80
  description: An easy to use ruby wrapper for Librato's Metrics API
70
81
  email: matt@librato.com
71
82
  executables: []
@@ -81,6 +92,7 @@ files:
81
92
  - README.md
82
93
  - Rakefile
83
94
  - lib/librato/metrics.rb
95
+ - lib/librato/metrics/collect.rb
84
96
  - lib/librato/metrics/errors.rb
85
97
  - lib/librato/metrics/persistence.rb
86
98
  - lib/librato/metrics/persistence/direct.rb
@@ -89,6 +101,7 @@ files:
89
101
  - lib/librato/metrics/simple.rb
90
102
  - lib/librato/metrics/version.rb
91
103
  - librato-metrics.gemspec
104
+ - spec/integration/metrics_spec.rb
92
105
  - spec/spec_helper.rb
93
106
  - spec/unit/metrics/queue_spec.rb
94
107
  - spec/unit/metrics/simple_spec.rb
@@ -119,6 +132,7 @@ signing_key:
119
132
  specification_version: 2
120
133
  summary: Ruby wrapper for Librato's Metrics API
121
134
  test_files:
135
+ - spec/integration/metrics_spec.rb
122
136
  - spec/spec_helper.rb
123
137
  - spec/unit/metrics/queue_spec.rb
124
138
  - spec/unit/metrics/simple_spec.rb