librato-metrics 0.4.3 → 0.5.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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