Easily log custom metrics to AWS CloudWatch

Recently, I wanted to track the amount of time that Sidekiq jobs were taking to execute on a production Rails environment. I wanted an easy way to be able to track the arguments for the Sidekiq job, the user that initiated the job, and of course the duration that the job took to complete.

Since this is a Rails application, the code provided is Ruby but you can look up the put_data method on the AWS docs for your respective programming language.

Generate the user and key #

We need to head over to the AWS dashboard and create a new IAM policy with the following JSON (provided in this blog post):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "cloudwatch:PutMetricData"
            ],
            "Resource": "*"
        }
    ]
}

I called this policy put-custom-metric.

Then, create an IAM user with programmatic access. Attach the custom put-custom-metric policy to this user. AWS will provide you with an access and secret key. Take note of these two strings as you’ll need them next.

Define the environment variables #

In your .bash_profile export the two new variables:

export cloudwatch_access_key_id='##your key here##'
export cloudwatch_secret_access_key='##your secret here##'

Save the file. Note: In your production environment, you’ll also have to define these two environment variables.

The code #

Insert the following in your Gemfile:

gem "aws-sdk-cloudwatch", require: false

And then execute bundle install to install the new package.

For my Rails application, I created a CloudWatchHelper module which is able to save the custom metrics to CloudWatch. I called the file cloudwatch.rb and placed it inside the /lib folder.

require "aws-sdk-cloudwatch"

module CloudWatchHelper

  def self.create_metric(some_arg, duration)
    options = {
      :access_key_id => ENV["cloudwatch_access_key_id"], 
      :secret_access_key => ENV["cloudwatch_secret_access_key"]
    }

    client = Aws::CloudWatch::Client.new({
      :region => "us-west-1",
      :access_key_id => options[:access_key_id],
      :secret_access_key => options[:secret_access_key]
    })

    # Get the environment based on ENV variables
    env = "development" if Rails.env.development?
    env = "production" if Rails.env.production?

    timestamp = Time.now.utc

    res = client.put_metric_data(
      namespace: "SidekiqJobTimes",
      metric_data: [
        {
          metric_name: "SidekiqDuration",
          dimensions: [{
              name: "Environment",
              value: env
            }, {
              name: "Arg",
              value: some_arg
            }],
          timestamp: timestamp,
          value: duration,
          unit: "Seconds"
        }
      ]
    )

    return true
  end

end

If you have other arguments you would like to send to CloudWatch along with the metric, simply add them into the dimensions array.

Next, I modified the application_job.rb file which is the parent class of all the Sidekiq jobs which get executed. The modification allows us to keep track of the start and end time of a job.

class ApplicationJob < ActiveJob::Base
  attr_accessor :before, :after, :duration

  def start_timer 
    @before = Time.now
  end

  def end_timer 
    @after = Time.now
    @duration = @after - @before
  end
end

The last thing to do is utilize the start_timer, end_timer, and create_metric functions within the actual job code.

require 'cloudwatch'

class HistoricalDataJob < ApplicationJob
  include CloudWatchHelper

  queue_as :default

  def perform(arg)
    # Start the timer for as soon as a job is created
    start_timer()

    # Do some work in your job
    some_work(arg)

    # End the timer after the job executes
    end_timer()

    # Create the metric in CloudWatch
    CloudWatchHelper.create_metric(arg, @duration)
  end
end

Testing your work #

Execute your job and within a minute of your Sidekiq job completing, you should start seeing your custom CloudWatch metrics coming into the CloudWatch AWS dashboard:

cloudwatch.png

Feel free to let me know on twitter (@shahzebdev) if you found this helpful 😊.

 
1
Kudos
 
1
Kudos

Now read this

Dynamically render React components

Recently on my team, I was tasked with figuring out how to dynamically render React components from strings that represented the component names. For instance, given const str = "Hello"; I would have to render the <Hello/>... Continue →