librato-metrics 0.4.3 → 0.5.0.pre1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## Changelog
2
2
 
3
+ ### Version 0.5.0
4
+ * Support using multiple accounts simultaneously via Client
5
+ * Switch network library to faraday for broader platform support and flexibility
6
+ * Automatic retry support
7
+ * Consolidate connection functions in Connection
8
+ * Documentation improvements
9
+
3
10
  ### Version 0.4.3
4
11
  * Bump excon to 0.13x to fix proxy support
5
12
 
data/README.md CHANGED
@@ -107,6 +107,35 @@ Get the 20 most recent 15 minute data point rollups for `temperature`:
107
107
 
108
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.
109
109
 
110
+ ## Using Multiple Accounts Simultaneously
111
+
112
+ If you need to use metrics with multiple sets of authentication credentials simultaneously, you can do it with `Client`:
113
+
114
+ joe = Librato::Metrics::Client.new
115
+ joe.authenticate 'email1', 'api_key1'
116
+
117
+ mike = Librato::Metrics::Client.new
118
+ mike.authenticate 'email2', 'api_key2'
119
+
120
+ All of the same operations you can call directly from `Librato::Metrics` are available per-client:
121
+
122
+ # list Joe's metrics
123
+ joe.list
124
+
125
+ # fetch the last 20 data points for Mike's metric, humidity
126
+ mike.fetch :humidity, :count => 20
127
+
128
+ There are two ways to associate a new queue with a client:
129
+
130
+ # these are functionally equivalent
131
+ joe_queue = Librato::Metrics::Queue.new(:client => joe)
132
+ joe_queue = joe.new_queue
133
+
134
+ Once the queue is associated you can use it normally:
135
+
136
+ joe_queue.add :temperature => {:source => 'sf', :value => 65.2}
137
+ joe_queue.submit
138
+
110
139
  ## Thread Safety
111
140
 
112
141
  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.
@@ -0,0 +1,180 @@
1
+ module Librato
2
+ module Metrics
3
+
4
+ class Client
5
+ attr_accessor :email, :api_key
6
+
7
+ # Provide agent identifier for the developer program. See:
8
+ # http://support.metrics.librato.com/knowledgebase/articles/53548-developer-program
9
+ #
10
+ # @example Have the gem build your identifier string
11
+ # Librato::Metrics.agent_identifier 'flintstone', '0.5', 'fred'
12
+ #
13
+ # @example Provide your own identifier string
14
+ # Librato::Metrics.agent_identifier 'flintstone/0.5 (dev_id:fred)'
15
+ #
16
+ # @example Remove identifier string
17
+ # Librato::Metrics.agent_identifier ''
18
+ def agent_identifier(*args)
19
+ if args.length == 1
20
+ @agent_identifier = args.first
21
+ elsif args.length == 3
22
+ @agent_identifier = "#{args[0]}/#{args[1]} (dev_id:#{args[2]})"
23
+ elsif ![0,1,3].include?(args.length)
24
+ raise ArgumentError, 'invalid arguments, see method documentation'
25
+ end
26
+ @agent_identifier ||= ''
27
+ end
28
+
29
+ # API endpoint to use for queries and direct
30
+ # persistence.
31
+ #
32
+ # @return [String] api_endpoint
33
+ def api_endpoint
34
+ @api_endpoint ||= 'https://metrics-api.librato.com'
35
+ end
36
+
37
+ # Set API endpoint for use with queries and direct
38
+ # persistence. Generally you should not need to set this
39
+ # as it will default to the current Librato Metrics
40
+ # endpoint.
41
+ #
42
+ def api_endpoint=(endpoint)
43
+ @api_endpoint = endpoint
44
+ end
45
+
46
+ # Authenticate for direct persistence
47
+ #
48
+ # @param [String] email
49
+ # @param [String] api_key
50
+ def authenticate(email, api_key)
51
+ flush_authentication
52
+ self.email, self.api_key = email, api_key
53
+ end
54
+
55
+ # Current connection object
56
+ #
57
+ def connection
58
+ # prevent successful creation if no credentials set
59
+ raise CredentialsMissing unless (self.email and self.api_key)
60
+ @connection ||= Connection.new(:client => self, :api_endpoint => api_endpoint)
61
+ end
62
+
63
+ # Query metric data
64
+ #
65
+ # @example Get attributes for a metric
66
+ # attrs = Librato::Metrics.fetch :temperature
67
+ #
68
+ # @example Get 20 most recent data points for metric
69
+ # data = Librato::Metrics.fetch :temperature, :count => 20
70
+ #
71
+ # @example Get 20 most recent data points for a specific source
72
+ # data = Librato::Metrics.fetch :temperature, :count => 20,
73
+ # :source => 'app1'
74
+ #
75
+ # @example Get the 20 most recent 15 minute data point rollups
76
+ # data = Librato::Metrics.fetch :temperature, :count => 20,
77
+ # :resolution => 900
78
+ #
79
+ # @example Get data points for the last hour
80
+ # data = Librato::Metrics.fetch :start_time => Time.now-3600
81
+ #
82
+ # @example Get 15 min data points from two hours to an hour ago
83
+ # data = Librato::Metrics.fetch :start_time => Time.now-7200,
84
+ # :end_time => Time.now-3600,
85
+ # :resolution => 900
86
+ #
87
+ # A full list of query parameters can be found in the API
88
+ # documentation: {http://dev.librato.com/v1/get/gauges/:name}
89
+ #
90
+ # @param [Symbol|String] metric Metric name
91
+ # @param [Hash] options Query options
92
+ def fetch(metric, options={})
93
+ query = options.dup
94
+ if query[:start_time].respond_to?(:year)
95
+ query[:start_time] = query[:start_time].to_i
96
+ end
97
+ if query[:end_time].respond_to?(:year)
98
+ query[:end_time] = query[:end_time].to_i
99
+ end
100
+ unless query.empty?
101
+ query[:resolution] ||= 1
102
+ end
103
+ # expects 200
104
+ url = connection.build_url("metrics/#{metric}", query)
105
+ response = connection.get(url)
106
+ parsed = MultiJson.decode(response.body)
107
+ # TODO: pagination support
108
+ query.empty? ? parsed : parsed["measurements"]
109
+ end
110
+
111
+ # Purge current credentials and connection.
112
+ #
113
+ def flush_authentication
114
+ self.email = nil
115
+ self.api_key = nil
116
+ @connection = nil
117
+ end
118
+
119
+ # List currently existing metrics
120
+ #
121
+ # @example List all metrics
122
+ # Librato::Metrics.list
123
+ #
124
+ # @example List metrics with 'foo' in the name
125
+ # Librato::Metrics.list :name => 'foo'
126
+ #
127
+ # @param [Hash] options
128
+ def list(options={})
129
+ query = {}
130
+ query[:name] = options[:name] if options[:name]
131
+ offset = 0
132
+ path = "metrics"
133
+ Collection.paginated_metrics(connection, path, query)
134
+ end
135
+
136
+ # Create a new queue which uses this client.
137
+ #
138
+ # @return [Queue]
139
+ def new_queue
140
+ Queue.new(:client => self)
141
+ end
142
+
143
+ # Persistence type to use when saving metrics.
144
+ # Default is :direct.
145
+ #
146
+ # @return [Symbol]
147
+ def persistence
148
+ @persistence ||= :direct
149
+ end
150
+
151
+ # Set persistence type to use when saving metrics.
152
+ #
153
+ # @param [Symbol] persistence_type
154
+ def persistence=(persist_method)
155
+ @persistence = persist_method
156
+ end
157
+
158
+ # Current persister object.
159
+ def persister
160
+ @queue ? @queue.persister : nil
161
+ end
162
+
163
+ # Submit all queued metrics.
164
+ #
165
+ def submit(args)
166
+ @queue ||= Queue.new(:client => self, :skip_measurement_times => true)
167
+ @queue.add args
168
+ @queue.submit
169
+ end
170
+
171
+ private
172
+
173
+ def flush_persistence
174
+ @persistence = nil
175
+ end
176
+
177
+ end
178
+
179
+ end
180
+ end
@@ -1,6 +1,6 @@
1
1
  module Librato
2
2
  module Metrics
3
- class Collect
3
+ class Collection
4
4
 
5
5
  MAX_RESULTS = 100
6
6
 
@@ -9,17 +9,19 @@ module Librato
9
9
  # @param [Excon] connection Connection to Metrics service
10
10
  # @param [String] path API uri
11
11
  # @param [Hash] query Query options
12
- def self.paginated_metrics connection, path, query
12
+ def self.paginated_metrics(connection, path, query)
13
13
  results = []
14
- response = connection.get(:path => path,
15
- :query => query, :expects => 200)
14
+ # expects 200
15
+ url = connection.build_url(path, query)
16
+ response = connection.get(url)
16
17
  parsed = MultiJson.decode(response.body)
17
18
  results = parsed["metrics"]
18
19
  return results if parsed["query"]["found"] <= MAX_RESULTS
19
20
  query[:offset] = MAX_RESULTS
20
21
  begin
21
- response = connection.get(:path => path,
22
- :query => query, :expects => 200)
22
+ # expects 200
23
+ url = connection.build_url(path, query)
24
+ response = connection.get(url)
23
25
  parsed = MultiJson.decode(response.body)
24
26
  results.push(*parsed["metrics"])
25
27
  query[:offset] += MAX_RESULTS
@@ -0,0 +1,91 @@
1
+ require 'faraday'
2
+ require 'metrics/middleware/expects_status'
3
+ require 'metrics/middleware/retry'
4
+
5
+ module Librato
6
+ module Metrics
7
+
8
+ class Connection
9
+ extend Forwardable
10
+
11
+ DEFAULT_API_ENDPOINT = 'https://metrics-api.librato.com'
12
+
13
+ def_delegators :transport, :get, :post, :head, :put, :delete,
14
+ :build_url
15
+
16
+ def initialize(options={})
17
+ @client = options[:client]
18
+ @api_endpoint = options[:api_endpoint]
19
+ end
20
+
21
+ # API endpoint that will be used for requests.
22
+ #
23
+ def api_endpoint
24
+ @api_endpoint || DEFAULT_API_ENDPOINT
25
+ end
26
+
27
+ def transport
28
+ raise NoClientProvided unless @client
29
+ @transport ||= Faraday::Connection.new(:url => api_endpoint + "/v1/") do |f|
30
+ #f.use FaradayMiddleware::EncodeJson
31
+ f.use Librato::Metrics::Middleware::Retry
32
+ f.use Librato::Metrics::Middleware::ExpectsStatus
33
+ #f.use FaradayMiddleware::ParseJson, :content_type => /\bjson$/
34
+ f.adapter Faraday.default_adapter
35
+ end.tap do |transport|
36
+ transport.headers[:user_agent] = user_agent
37
+ transport.headers[:content_type] = 'application/json'
38
+ transport.basic_auth @client.email, @client.api_key
39
+ end
40
+ end
41
+
42
+ # User-agent used when making requests.
43
+ #
44
+ def user_agent
45
+ ua_chunks = []
46
+ agent_identifier = @client.agent_identifier
47
+ if agent_identifier && !agent_identifier.empty?
48
+ ua_chunks << agent_identifier
49
+ end
50
+ ua_chunks << "librato-metrics/#{Metrics::VERSION}"
51
+ ua_chunks << "(#{ruby_engine}; #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}; #{RUBY_PLATFORM})"
52
+ ua_chunks << "direct-faraday/#{Faraday::VERSION}"
53
+ # TODO: include adapter information
54
+ #ua_chunks << "(#{transport_adapter})"
55
+ ua_chunks.join(' ')
56
+ end
57
+
58
+ private
59
+
60
+ def adapter_version
61
+ adapter = transport_adapter
62
+ case adapter
63
+ when "NetHttp"
64
+ "Net::HTTP/#{Net::HTTP::Revision}"
65
+ when "Typhoeus"
66
+ "typhoeus/#{Typhoeus::VERSION}"
67
+ when "Excon"
68
+ "excon/#{Excon::VERSION}"
69
+ else
70
+ adapter
71
+ end
72
+ end
73
+
74
+ def ruby_engine
75
+ return RUBY_ENGINE if Object.constants.include?(:RUBY_ENGINE)
76
+ RUBY_DESCRIPTION.split[0]
77
+ end
78
+
79
+ # figure out which adapter faraday is using
80
+ def transport_adapter
81
+ transport.builder.handlers.each do |handler|
82
+ if handler.name[0,16] == "Faraday::Adapter"
83
+ return handler.name[18..-1]
84
+ end
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
@@ -7,6 +7,7 @@ module Librato
7
7
  class CredentialsMissing < MetricsError; end
8
8
  class AgentInfoMissing < MetricsError; end
9
9
  class NoMetricsQueued < MetricsError; end
10
+ class NoClientProvided < MetricsError; end
10
11
 
11
12
  end
12
13
  end
@@ -0,0 +1,22 @@
1
+ module Librato
2
+ module Metrics
3
+ module Middleware
4
+
5
+ class ExpectsStatus < Faraday::Response::Middleware
6
+
7
+ def on_complete(env)
8
+ # TODO: clean up exception output
9
+ # TODO: catch specific status codes by request
10
+ case env[:status]
11
+ when 404
12
+ raise Faraday::Error::ResourceNotFound, env.to_s
13
+ when 400..600
14
+ raise Faraday::Error::ClientError, env.to_s
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # borrowed from faraday 0.8, will use official once final
2
+ module Librato
3
+ module Metrics
4
+ module Middleware
5
+
6
+ class Retry < Faraday::Middleware
7
+
8
+ def initialize(app, retries = 3)
9
+ @retries = retries
10
+ super(app)
11
+ end
12
+
13
+ def call(env)
14
+ retries = @retries
15
+ @app.call(env)
16
+ rescue StandardError, Timeout::Error
17
+ if retries > 0
18
+ retries -= 1
19
+ retry
20
+ end
21
+ raise
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -9,11 +9,10 @@ module Librato
9
9
  # Persist the queued metrics directly to the
10
10
  # Metrics web API.
11
11
  #
12
- def persist(queued)
12
+ def persist(client, queued)
13
13
  payload = MultiJson.encode(queued)
14
- Simple.connection.post(:path => '/v1/metrics',
15
- :headers => {'Content-Type' => 'application/json'},
16
- :body => payload, :expects => 200)
14
+ # expects 200
15
+ client.connection.post('metrics', payload)
17
16
  end
18
17
 
19
18
  end
@@ -6,7 +6,7 @@ module Librato
6
6
  class Test
7
7
 
8
8
  # persist the given metrics
9
- def persist(metrics)
9
+ def persist(client, metrics)
10
10
  @persisted = metrics
11
11
  return !@return_value.nil? ? @return_value : true
12
12
  end
@@ -7,6 +7,7 @@ module Librato
7
7
  def initialize(options={})
8
8
  @queued ||= {}
9
9
  @skip_measurement_times = options.delete(:skip_measurement_times)
10
+ @client = options.delete(:client) || Librato::Metrics.client
10
11
  end
11
12
 
12
13
  # Add a metric entry to the metric set:
@@ -33,9 +34,19 @@ module Librato
33
34
  queued
34
35
  end
35
36
 
37
+ # The current Client instance this queue is using to authenticate
38
+ # and connect to Librato Metrics. This will default to the primary
39
+ # client used by the Librato::Metrics module unless it has been
40
+ # set to something else.
41
+ #
42
+ # @return [Librato::Metrics::Client]
43
+ def client
44
+ @client ||= Librato::Metrics.client
45
+ end
46
+
36
47
  # Currently queued counters
37
48
  #
38
- # @return Array
49
+ # @return [Array]
39
50
  def counters
40
51
  @queued[:counters] || []
41
52
  end
@@ -88,7 +99,7 @@ module Librato
88
99
  # @return Boolean
89
100
  def submit
90
101
  raise NoMetricsQueued if self.queued.empty?
91
- if persister.persist(self.queued)
102
+ if persister.persist(self.client, self.queued)
92
103
  flush and return true
93
104
  end
94
105
  false
@@ -124,7 +135,7 @@ module Librato
124
135
  private
125
136
 
126
137
  def create_persister
127
- type = Simple.persistence.to_s.capitalize
138
+ type = self.client.persistence.to_s.capitalize
128
139
  Librato::Metrics::Persistence.const_get(type).new
129
140
  end
130
141
 
@@ -1,5 +1,5 @@
1
1
  module Librato
2
2
  module Metrics
3
- VERSION = "0.4.3"
3
+ VERSION = "0.5.0.pre1"
4
4
  end
5
5
  end
@@ -2,22 +2,62 @@ $:.unshift(File.dirname(__FILE__)) unless
2
2
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
3
 
4
4
  require 'base64'
5
- require 'excon'
6
5
  require 'multi_json'
7
6
 
7
+ require 'metrics/client'
8
+ require 'metrics/collection'
9
+ require 'metrics/connection'
8
10
  require 'metrics/errors'
9
11
  require 'metrics/persistence'
10
12
  require 'metrics/queue'
11
- require 'metrics/simple'
12
13
  require 'metrics/version'
13
- require 'metrics/collect'
14
14
 
15
15
  module Librato
16
16
 
17
- # Metrics provides a simple wrapper for the Metrics web API. Some
18
- # of the methods Metrics provides will be documented below. Others
19
- # are delegated to {Librato::Metrics::Simple} and will be
20
- # documented there.
17
+ # Metrics provides a simple wrapper for the Metrics web API with a
18
+ # number of added conveniences for common use cases.
19
+ #
20
+ # See the {file:README.md README} for more information and examples.
21
+ #
22
+ # @example Simple use case
23
+ # Librato::Metrics.authenticate 'email', 'api_key'
24
+ #
25
+ # # list current metrics
26
+ # Librato::Metrics.list
27
+ #
28
+ # # submit a metric immediately
29
+ # Librato::Metrics.submit :foo => 12712
30
+ #
31
+ # # fetch the last 10 values of foo
32
+ # Librato::Metrics.fetch :foo, :count => 10
33
+ #
34
+ # @example Queuing metrics for submission
35
+ # queue = Librato::Metrics::Queue.new
36
+ #
37
+ # # queue some metrics
38
+ # queue.add :foo => 12312
39
+ # queue.add :bar => 45678
40
+ #
41
+ # # send the metrics
42
+ # queue.submit
43
+ #
44
+ # @example Using a Client object
45
+ # client = Librato::Metrics::Client.new
46
+ # client.authenticate 'email', 'api_key'
47
+ #
48
+ # # list client's metrics
49
+ # client.list
50
+ #
51
+ # # create an associated queue
52
+ # queue = client.new_queue
53
+ #
54
+ # # queue up some metrics and submit
55
+ # queue.add :foo => 12345
56
+ # queue.add :bar => 45678
57
+ # queue.submit
58
+ #
59
+ # @note Most of the methods you can call directly on Librato::Metrics are
60
+ # delegated to {Client} and are documented there.
21
61
  module Metrics
22
62
  extend SingleForwardable
23
63
 
@@ -25,73 +65,16 @@ module Librato
25
65
 
26
66
  # Expose class methods of Simple via Metrics itself.
27
67
  #
28
- # TODO: Explain exposed interface with examples.
29
- def_delegators Librato::Metrics::Simple, :agent_identifier, :api_endpoint,
30
- :api_endpoint=, :authenticate, :connection, :persistence,
31
- :persistence=, :persister, :submit
68
+ def_delegators :client, :agent_identifier, :api_endpoint,
69
+ :api_endpoint=, :authenticate, :connection, :fetch,
70
+ :list, :persistence, :persistence=, :persister, :submit
32
71
 
33
- # Query metric data
72
+ # The Librato::Metrics::Client being used by module-level
73
+ # access.
34
74
  #
35
- # @example Get attributes for a metric
36
- # attrs = Librato::Metrics.fetch :temperature
37
- #
38
- # @example Get 20 most recent data points for metric
39
- # data = Librato::Metrics.fetch :temperature, :count => 20
40
- #
41
- # @example Get 20 most recent data points for a specific source
42
- # data = Librato::Metrics.fetch :temperature, :count => 20,
43
- # :source => 'app1'
44
- #
45
- # @example Get the 20 most recent 15 minute data point rollups
46
- # data = Librato::Metrics.fetch :temperature, :count => 20,
47
- # :resolution => 900
48
- #
49
- # @example Get data points for the last hour
50
- # data = Librato::Metrics.fetch :start_time => Time.now-3600
51
- #
52
- # @example Get 15 min data points from two hours to an hour ago
53
- # data = Librato::Metrics.fetch :start_time => Time.now-7200,
54
- # :end_time => Time.now-3600,
55
- # :resolution => 900
56
- #
57
- # A full list of query parameters can be found in the API
58
- # documentation: {http://dev.librato.com/v1/get/gauges/:name}
59
- #
60
- # @param [Symbol|String] metric Metric name
61
- # @param [Hash] options Query options
62
- def self.fetch(metric, options={})
63
- query = options.dup
64
- if query[:start_time].respond_to?(:year)
65
- query[:start_time] = query[:start_time].to_i
66
- end
67
- if query[:end_time].respond_to?(:year)
68
- query[:end_time] = query[:end_time].to_i
69
- end
70
- unless query.empty?
71
- query[:resolution] ||= 1
72
- end
73
- response = connection.get(:path => "v1/metrics/#{metric}",
74
- :query => query, :expects => 200)
75
- parsed = MultiJson.decode(response.body)
76
- # TODO: pagination support
77
- query.empty? ? parsed : parsed["measurements"]
78
- end
79
-
80
- # List currently existing metrics
81
- #
82
- # @example List all metrics
83
- # Librato::Metrics.list
84
- #
85
- # @example List metrics with 'foo' in the name
86
- # Librato::Metrics.list :name => 'foo'
87
- #
88
- # @param [Hash] options
89
- def self.list(options={})
90
- query = {}
91
- query[:name] = options[:name] if options[:name]
92
- offset = 0
93
- path = "v1/metrics"
94
- Collect.paginated_metrics connection, path, query
75
+ # @return [Client]
76
+ def self.client
77
+ @client ||= Librato::Metrics::Client.new
95
78
  end
96
79
 
97
80
  end
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
9
  s.rubygems_version = '1.3.5'
10
10
 
11
- s.name = 'librato-metrics'
12
- s.version = Librato::Metrics::VERSION
11
+ s.name = 'librato-metrics'
12
+ s.version = Librato::Metrics::VERSION
13
13
 
14
14
  s.summary = "Ruby wrapper for Librato's Metrics API"
15
15
  s.description = "An easy to use ruby wrapper for Librato's Metrics API"
@@ -24,12 +24,13 @@ Gem::Specification.new do |s|
24
24
  s.extra_rdoc_files = %w[LICENSE]
25
25
 
26
26
  ## runtime dependencies
27
- s.add_dependency 'excon', '~> 0.13.0'
27
+ s.add_dependency 'faraday', '~>0.7.6'
28
28
  s.add_dependency 'multi_json'
29
29
 
30
30
  ## development dependencies
31
31
  s.add_development_dependency 'rake'
32
32
  s.add_development_dependency 'rspec', '~>2.6.0'
33
+ s.add_development_dependency 'pry'
33
34
  s.add_development_dependency 'yard'
34
35
  s.add_development_dependency 'rdiscount' # for yard
35
36
 
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ module Librato
4
+ module Metrics
5
+
6
+ describe Connection do
7
+
8
+ # TODO: test retry support
9
+ # TODO: test status code support
10
+
11
+ end
12
+
13
+ end
14
+ end