lib/librato/metrics/client.rb
module Librato
module Metrics
class Client
extend Forwardable
def_delegator :annotator, :add, :annotate
attr_accessor :email, :api_key, :proxy
# Provide agent identifier for the developer program. See:
# http://support.metrics.librato.com/knowledgebase/articles/53548-developer-program
#
# @example Have the gem build your identifier string
# Librato::Metrics.agent_identifier 'flintstone', '0.5', 'fred'
#
# @example Provide your own identifier string
# Librato::Metrics.agent_identifier 'flintstone/0.5 (dev_id:fred)'
#
# @example Remove identifier string
# Librato::Metrics.agent_identifier ''
def agent_identifier(*args)
if args.length == 1
@agent_identifier = args.first
elsif args.length == 3
@agent_identifier = "#{args[0]}/#{args[1]} (dev_id:#{args[2]})"
elsif ![0,1,3].include?(args.length)
raise ArgumentError, 'invalid arguments, see method documentation'
end
@agent_identifier ||= ''
end
def annotator
@annotator ||= Annotator.new(client: self)
end
# API endpoint to use for queries and direct
# persistence.
#
# @return [String] api_endpoint
def api_endpoint
@api_endpoint ||= 'https://metrics-api.librato.com'
end
# Set API endpoint for use with queries and direct
# persistence. Generally you should not need to set this
# as it will default to the current Librato Metrics
# endpoint.
#
def api_endpoint=(endpoint)
@api_endpoint = endpoint
end
# Authenticate for direct persistence
#
# @param [String] email
# @param [String] api_key
def authenticate(email, api_key)
flush_authentication
self.email, self.api_key = email, api_key
end
# Current connection object
#
def connection
# prevent successful creation if no credentials set
raise CredentialsMissing unless (self.email and self.api_key)
@connection ||= Connection.new(client: self, api_endpoint: api_endpoint,
adapter: faraday_adapter, proxy: self.proxy)
end
# Overrride user agent for this client's connections. If you
# are trying to specify an agent identifier for developer
# program, see #agent_identifier.
#
def custom_user_agent=(agent)
@user_agent = agent
@connection = nil
end
def custom_user_agent
@user_agent
end
# Completely delete metrics with the given names. Be
# careful with this, this is instant and permanent.
#
# @example Delete metric 'temperature'
# Librato::Metrics.delete_metrics :temperature
#
# @example Delete metrics 'foo' and 'bar'
# Librato::Metrics.delete_metrics :foo, :bar
#
# @example Delete metrics that start with 'foo' except 'foobar'
# Librato::Metrics.delete_metrics names: 'foo*', exclude: ['foobar']
#
def delete_metrics(*metric_names)
raise(NoMetricsProvided, 'Metric name missing.') if metric_names.empty?
if metric_names[0].respond_to?(:keys) # hash form
params = metric_names[0]
else
params = { names: metric_names.map(&:to_s) }
end
connection.delete do |request|
request.url connection.build_url("metrics")
request.body = SmartJSON.write(params)
end
# expects 204, middleware will raise exception otherwise.
true
end
# Return current adapter this client will use.
# Defaults to Metrics.faraday_adapter if set, otherwise
# Faraday.default_adapter
def faraday_adapter
@faraday_adapter ||= default_faraday_adapter
end
# Set faraday adapter this client will use
def faraday_adapter=(adapter)
@faraday_adapter = adapter
end
# Retrieve measurements for a given composite metric definition.
# :start_time and :resolution are required options, :end_time is
# optional.
#
# @example Get 5m moving average of 'foo'
# measurements = Librato::Metrics.get_composite
# 'moving_average(mean(series("foo", "*"), {size: "5"}))',
# start_time: Time.now.to_i - 60*60, resolution: 300
#
# @param [String] definition Composite definition
# @param [hash] options Query options
def get_composite(definition, options={})
unless options[:start_time] && options[:resolution]
raise "You must provide a :start_time and :resolution"
end
query = options.dup
query[:compose] = definition
url = connection.build_url("metrics", query)
response = connection.get(url)
parsed = SmartJSON.read(response.body)
# TODO: pagination support
parsed
end
# Retrieve a specific metric by name, optionally including data points
#
# @example Get attributes for a metric
# metric = Librato::Metrics.get_metric :temperature
#
# @example Get a metric and its 20 most recent data points
# metric = Librato::Metrics.get_metric :temperature, count: 20
# metric['measurements'] # => {...}
#
# A full list of query parameters can be found in the API
# documentation: {http://dev.librato.com/v1/get/metrics/:name}
#
# @param [Symbol|String] name Metric name
# @param [Hash] options Query options
def get_metric(name, options = {})
query = options.dup
if query[:start_time].respond_to?(:year)
query[:start_time] = query[:start_time].to_i
end
if query[:end_time].respond_to?(:year)
query[:end_time] = query[:end_time].to_i
end
unless query.empty?
query[:resolution] ||= 1
end
# expects 200
url = connection.build_url("metrics/#{name}", query)
response = connection.get(url)
parsed = SmartJSON.read(response.body)
# TODO: pagination support
parsed
end
# Retrieve series of measurements for a given metric
#
# @example Get series for metric
# series = Librato::Metrics.get_series :requests, resolution: 1, duration: 3600
#
# @example Get series for metric grouped by tag
# query = { duration: 3600, resolution: 1, group_by: "environment", group_by_function: "sum" }
# series = Librato::Metrics.get_series :requests, query
#
# @example Get series for metric grouped by tag and negated by tag filter
# query = { duration: 3600, resolution: 1, group_by: "environment", group_by_function: "sum", tags_search: "environment=!staging" }
# series = Librato::Metrics.get_series :requests, query
#
# @param [Symbol|String] metric_name Metric name
# @param [Hash] options Query options
def get_series(metric_name, options={})
raise ArgumentError, ":resolution and :duration or :start_time must be set" if options.empty?
query = options.dup
if query[:start_time].respond_to?(:year)
query[:start_time] = query[:start_time].to_i
end
if query[:end_time].respond_to?(:year)
query[:end_time] = query[:end_time].to_i
end
query[:resolution] ||= 1
unless query[:start_time] || query[:end_time]
query[:duration] ||= 3600
end
url = connection.build_url("measurements/#{metric_name}", query)
response = connection.get(url)
parsed = SmartJSON.read(response.body)
parsed["series"]
end
# Retrieve data points for a specific metric
#
# @example Get 20 most recent data points for metric
# data = Librato::Metrics.get_measurements :temperature, count: 20
#
# @example Get 20 most recent data points for a specific source
# data = Librato::Metrics.get_measurements :temperature, count: 20,
# source: 'app1'
#
# @example Get the 20 most recent 15 minute data point rollups
# data = Librato::Metrics.get_measurements :temperature, count: 20,
# resolution: 900
#
# @example Get data points for the last hour
# data = Librato::Metrics.get_measurements start_time: Time.now-3600
#
# @example Get 15 min data points from two hours to an hour ago
# data = Librato::Metrics.get_measurements start_time: Time.now-7200,
# end_time: Time.now-3600,
# resolution: 900
#
# A full list of query parameters can be found in the API
# documentation: {http://dev.librato.com/v1/get/metrics/:name}
#
# @param [Symbol|String] metric_name Metric name
# @param [Hash] options Query options
def get_measurements(metric_name, options = {})
raise ArgumentError, "you must provide at least a :start_time or :count" if options.empty?
get_metric(metric_name, options)["measurements"]
end
# Purge current credentials and connection.
#
def flush_authentication
self.email = nil
self.api_key = nil
@connection = nil
end
# List currently existing metrics
#
# @example List all metrics
# Librato::Metrics.metrics
#
# @example List metrics with 'foo' in the name
# Librato::Metrics.metrics name: 'foo'
#
# @param [Hash] options
def metrics(options={})
query = {}
query[:name] = options[:name] if options[:name]
offset = 0
path = "metrics"
Collection.paginated_metrics(connection, path, query)
end
# Create a new queue which uses this client.
#
# @return [Queue]
def new_queue(options={})
options[:client] = self
Queue.new(options)
end
# Persistence type to use when saving metrics.
# Default is :direct.
#
# @return [Symbol]
def persistence
@persistence ||= :direct
end
# Set persistence type to use when saving metrics.
#
# @param [Symbol] persist_method
def persistence=(persist_method)
@persistence = persist_method
end
# Current persister object.
def persister
@queue ? @queue.persister : nil
end
# Submit all queued metrics.
#
def submit(args)
@queue ||= Queue.new(client: self,
skip_measurement_times: true,
clear_failures: true)
@queue.add args
@queue.submit
end
# Update a single metric with new attributes.
#
# @example Update metric 'temperature'
# Librato::Metrics.update_metric :temperature, period: 15, attributes: { color: 'F00' }
#
# @example Update metric 'humidity', creating it if it doesn't exist
# Librato::Metrics.update_metric 'humidity', type: :gauge, period: 60, display_name: 'Humidity'
#
def update_metric(metric, options = {})
url = "metrics/#{metric}"
connection.put do |request|
request.url connection.build_url(url)
request.body = SmartJSON.write(options)
end
end
# Update multiple metrics.
#
# @example Update multiple metrics by name
# Librato::Metrics.update_metrics names: ["foo", "bar"], period: 60
#
# @example Update all metrics that start with 'foo' that aren't 'foobar'
# Librato::Metrics.update_metrics names: 'foo*', exclude: ['foobar'], display_min: 0
#
def update_metrics(metrics)
url = "metrics" # update multiple metrics
connection.put do |request|
request.url connection.build_url(url)
request.body = SmartJSON.write(metrics)
end
end
# List sources, optionally limited by a name. See http://dev.librato.com/v1/sources
# and http://dev.librato.com/v1/get/sources
#
# @example Get sources matching "production"
# Librato::Metrics.sources name: "production"
#
# @param [Hash] filter
def sources(filter = {})
query = {}
query[:name] = filter[:name] if filter.has_key?(:name)
path = "sources"
Collection.paginated_collection("sources", connection, path, query)
end
# Retrieve a single source by name. See http://dev.librato.com/v1/get/sources/:name
#
# @example Get the source for a particular EC2 instance from Cloudwatch
# Librato::Metrics.get_source "us-east-1b.i-f1bc8c9c"
#
# @param String name
def get_source(name)
url = connection.build_url("sources/#{name}")
response = connection.get(url)
parsed = SmartJSON.read(response.body)
end
# Update a source by name. See http://dev.librato.com/v1/get/sources/:name
#
# @example Update the source display name for a particular EC2 instance from Cloudwatch
# Librato::Metrics.update_source "us-east-1b.i-f1bc8c9c", display_name: "Production Web 1"
#
# @param String name
# @param Hash options
def update_source(name, options = {})
url = "sources/#{name}"
connection.put do |request|
request.url connection.build_url(url)
request.body = SmartJSON.write(options)
end
end
# Create a snapshot of an instrument
#
# @example Take a snapshot of the instrument at https://metrics-api.librato.com/v1/instruments/42 using a
# duration of 3 hours and ending at now.
# Librato::Metrics.snapshot(subject: {instrument: {href: "https://metrics-api.librato.com/v1/instruments/42"}},
# duration: 3.hours, end_time: Time.now)
#
# @param Hash options Params for the snapshot
# @options options [Hash] :subject An object representing the subject of the snapshot. For now, only instruments are supported
# @options options [Numeric] :duration Time interval over which to take the snapshot, defaults to 1 hour
# @options options [Numeric, Time] :end_time Snapshot the time period of the duration, ending with end_time. Default is "now".
def create_snapshot(options = {})
url = "snapshots"
response = connection.post do |request|
request.url connection.build_url(url)
request.body = SmartJSON.write(options)
end
parsed = SmartJSON.read(response.body)
end
# Retrive a snapshot, to check its progress or find its image_href
#
# @example Get a snapshot identified by 42
# Librato::Metrics.get_snapshot 42
#
# @param [Integer|String] id
def get_snapshot(id)
url = "snapshots/#{id}"
response = connection.get(url)
parsed = SmartJSON.read(response.body)
end
private
def default_faraday_adapter
if Metrics.client == self
Faraday.default_adapter
else
Metrics.faraday_adapter
end
end
def flush_persistence
@persistence = nil
end
end
end
end