Last active
April 3, 2023 13:54
-
-
Save stympy/763f0e75e5058a3c8e262d4c6ef8959e to your computer and use it in GitHub Desktop.
Rack middleware to send request metrics to CloudWatch Metrics
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "aws-sdk-cloudwatch" | |
require "descriptive_statistics/safe" | |
class CloudWatchMetricsMiddleware | |
def initialize(app, opts = {}) | |
@app = app | |
@client = Aws::CloudWatch::Client.new(region: opts[:region] || "us-east-1") | |
@counts = Queue.new | |
@timings = Queue.new | |
@mutex = Mutex.new | |
@sleep_time = opts[:sleep_time] || 60 | |
@namespace = opts[:namespace] || "MyApp" | |
@timer_thread = Thread.new { start_timer } | |
end | |
def call(env) | |
start_time = Time.now | |
response = @app.call(env) | |
duration_ms = ((Time.now - start_time) * 1000).to_i | |
# Get status code from response | |
status_code = response[0] | |
# Queue metrics to be sent to CloudWatch | |
@mutex.synchronize do | |
@counts.push({status_code: status_code, count: 1}) | |
@timings.push({status_code: status_code, duration_ms: duration_ms}) | |
end | |
response | |
end | |
def start_timer | |
loop do | |
sleep(@sleep_time) | |
send_metrics_to_cloudwatch | |
end | |
end | |
def send_metrics_to_cloudwatch | |
data_to_send = [] | |
counts_by_status_code = Hash.new(0) | |
timings_by_status_code = Hash.new { |h, k| h[k] = [] } | |
@mutex.synchronize do | |
until @counts.empty? | |
count = @counts.pop | |
counts_by_status_code[count[:status_code]] += count[:count] | |
end | |
until @timings.empty? | |
timing = @timings.pop | |
timings_by_status_code[timing[:status_code]] << timing[:duration_ms] | |
end | |
counts_by_status_code.each do |status_code, count| | |
data_to_send << { | |
metric_name: "RequestCount", | |
dimensions: [{name: "StatusCode", value: status_code.to_s}], | |
value: count, | |
unit: "Count" | |
} | |
end | |
timings_by_status_code.each do |status_code, timings| | |
data_to_send << { | |
metric_name: "ResponseTime", | |
dimensions: [{name: "StatusCode", value: status_code.to_s}], | |
statistic_values: { | |
sample_count: timings.size, | |
sum: timings.sum, | |
minimum: timings.min, | |
maximum: timings.max | |
}, | |
unit: "Milliseconds" | |
} | |
data_to_send << { | |
metric_name: "ResponseTime p90", | |
dimensions: [{name: "StatusCode", value: status_code.to_s}], | |
value: DescriptiveStatistics.percentile(90, timings), | |
unit: "Milliseconds" | |
} | |
end | |
end | |
return if data_to_send.empty? | |
data_to_send.each_slice(100) do |slice| | |
@client.put_metric_data(namespace: @namespace, metric_data: slice) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment