#! /usr/bin/env ruby
#
# elasticache-metrics
#
# DESCRIPTION:
#   Fetch Elasticache metrics from CloudWatch
#
# OUTPUT:
#   metric-data
#
# PLATFORMS:
#   Linux
#
# DEPENDENCIES:
#   gem: aws-sdk-v1
#   gem: sensu-plugin
#
# USAGE:
#   elasticache-metrics.rb -n rediscluster -c redis -a key -k secret
#   elasticache-metrics.rb -n memcachedcluster -c memcached -a key -k secret
#
# NOTES:
#   Redis: http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CacheMetrics.Redis.html
#   Memcached: http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CacheMetrics.Memcached.html
#
#   By default fetches all available statistics from one minute ago.  You may need to fetch further back than this;
#
# LICENSE:
#   Copyright 2014 Yann Verry
#   Released under the same terms as Sensu (the MIT license); see LICENSE
#   for details.
#

require 'sensu-plugin/metric/cli'
require 'aws-sdk-v1'

class ElastiCacheMetrics < Sensu::Plugin::Metric::CLI::Graphite
  option :cacheclusterid,
         description: 'Name of the Cache Cluster',
         short: '-n ELASTICACHE_NAME',
         long: '--name ELASTICACHE_NAME',
         required: true

  option :cachenodeid,
         description: 'Cache Node ID',
         short: '-i CACHE_NODE_ID',
         long: '--cache-node-id CACHE_NODE_ID',
         default: '0001'

  option :elasticachetype,
         description: 'Elasticache type redis or memcached',
         short: '-c TYPE',
         long: '--cachetype TYPE',
         required: true

  option :scheme,
         description: 'Metric naming scheme, text to prepend to metric',
         short: '-s SCHEME',
         long: '--scheme SCHEME',
         default: ''

  option :fetch_age,
         description: 'How long ago to fetch metrics for',
         short: '-f AGE',
         long: '--fetch_age',
         default: 60,
         proc: proc(&:to_i)

  option :aws_access_key,
         short: '-a AWS_ACCESS_KEY',
         long: '--aws-access-key AWS_ACCESS_KEY',
         description: "AWS Access Key. Either set ENV['AWS_ACCESS_KEY'] or provide it as an option",
         default: ENV['AWS_ACCESS_KEY']

  option :aws_secret_access_key,
         short: '-k AWS_SECRET_KEY',
         long: '--aws-secret-access-key AWS_SECRET_KEY',
         description: "AWS Secret Access Key. Either set ENV['AWS_SECRET_KEY'] or provide it as an option",
         default: ENV['AWS_SECRET_KEY']

  option :aws_region,
         short: '-r AWS_REGION',
         long: '--aws-region REGION',
         description: 'AWS Region (defaults to us-east-1).',
         default: 'us-east-1'

  def aws_config
    { access_key_id: config[:aws_access_key],
      secret_access_key: config[:aws_secret_access_key],
      region: config[:aws_region] }
  end

  def run
    graphitepath = if config[:scheme] == ''
                     "elasticache.#{config[:cacheclusterid]}"
                   else
                     config[:scheme]
                   end

    dimensions = if config[:cachenodeid]
                   [{
                     'name' => 'CacheClusterId',
                     'value' => config[:cacheclusterid]
                   }, {
                     'name' => 'CacheNodeId',
                     'value' => config[:cachenodeid]
                   }]
                 else
                   [{
                     'name' => 'CacheClusterId',
                     'value' => config[:cacheclusterid]
                   }]
                 end

    statistic_type = {
      'redis' => {
        'CPUUtilization' => 'Percent',
        'BytesUsedForCache' => 'Bytes',
        'SwapUsage' => 'Bytes',
        'FreeableMemory' => 'Bytes',
        'NetworkBytesIn' => 'Bytes',
        'NetworkBytesOut' => 'Bytes',
        'GetTypeCmds' => 'Count',
        'SetTypeCmds' => 'Count',
        'KeyBasedCmds' => 'Count',
        'StringBasedCmds' => 'Count',
        'HashBasedCmds' => 'Count',
        'ListBasedCmds' => 'Count',
        'SetBasedCmds' => 'Count',
        'SortedSetBasedCmds' => 'Count',
        'CurrItems' => 'Count'
      },
      'memcached' => {
        'CPUUtilization' => 'Percent',
        'SwapUsage' => 'Bytes',
        'FreeableMemory' => 'Bytes',
        'NetworkBytesIn' => 'Bytes',
        'NetworkBytesOut' => 'Bytes',
        'BytesUsedForCacheItems' => 'Bytes',
        'BytesReadIntoMemcached' => 'Bytes',
        'BytesWrittenOutFromMemcached' => 'Bytes',
        'CasBadval' => 'Count',
        'CasHits' => 'Count',
        'CasMisses' => 'Count',
        'CmdFlush' => 'Count',
        'CmdGet' => 'Count',
        'CmdSet' => 'Count',
        'CurrConnections' => 'Count',
        'CurrItems' => 'Count',
        'DecrHits' => 'Count',
        'DecrMisses' => 'Count',
        'DeleteHits' => 'Count',
        'DeleteMisses' => 'Count',
        'Evictions' => 'Count',
        'GetHits' => 'Count',
        'GetMisses' => 'Count',
        'IncrHits' => 'Count',
        'IncrMisses' => 'Count',
        'Reclaimed' => 'Count',
        'BytesUsedForHash' => 'Bytes',
        'CmdConfigGet' => 'Count',
        'CmdConfigSet' => 'Count',
        'CmdTouch' => 'Count',
        'CurrConfig' => 'Count',
        'EvictedUnfetched' => 'Count',
        'ExpiredUnfetched' => 'Count',
        'SlabsMoved' => 'Count',
        'TouchHits' => 'Count',
        'TouchMisses' => 'Count',
        'NewConnections' => 'Count',
        'NewItems' => 'Count',
        'UnusedMemory' => 'Bytes'
      }
    }

    begin
      et = Time.now - config[:fetch_age]
      st = et - 60

      cw = AWS::CloudWatch::Client.new aws_config

      # define all options
      options = {
        'namespace' => 'AWS/ElastiCache',
        'metric_name' => config[:metric],
        'dimensions' => dimensions,
        'start_time' => st.iso8601,
        'end_time' => et.iso8601,
        'period' => 60,
        'statistics' => ['Average']
      }

      result = {}

      # Fetch all metrics by elasticachetype (redis or memcached).
      statistic_type[config[:elasticachetype]].each do |m|
        options['metric_name'] = m[0] # override metric
        r = cw.get_metric_statistics(options)
        result[m[0]] = r[:datapoints][0] unless r[:datapoints][0].nil?
      end

      unless result.nil?
        result.each do |name, d|
          # We only return data when we have some to return
          output graphitepath + '.' + name.downcase, d[:average], d[:timestamp].to_i
        end
      end
    rescue => e
      puts "Error: exception: #{e}"
      critical
    end
    ok
  end
end