Skip to content

Commit 67f0526

Browse files
committed
feat: Add support for the 4 permutations of Elasticache redis endpoints
chore: move redis bits to subclass Fixes #5 See https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/Endpoints.html
1 parent 4db4034 commit 67f0526

File tree

8 files changed

+412
-147
lines changed

8 files changed

+412
-147
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The following parameters need to be supplied to the lambda function.
2323

2424
Environment Variable | Type | Description | Required | Default
2525
---------------------|--------|---------------------------------------------------------------|----------|---------
26-
REDIS_HOST | string | FQDN of the elasticache redis endpoint | yes | -
26+
REDIS_HOST | string | FQDN or URI of the elasticache redis endpoint | yes | -
2727
DATADOG_API_KEY | string | Datadog API Key | no | -
2828
DATADOG_APP_KEY | string | Datadog API Key | no | -
2929
NAMESPACE | string | "namespace" tag to apply to Datadog metric | yes | -
@@ -49,7 +49,7 @@ DO NOT use this script in production environments, as it will CPU thrash the tar
4949

5050
Environment Variable | Type | Description | Required | Default
5151
---------------------|--------|---------------------------------------------------------------|----------|---------
52-
REDIS_HOST | string | FQDN of the elasticache redis endpoint | yes | -
52+
REDIS_HOST | string | FQDN or URI of the elasticache redis endpoint | yes | -
5353

5454

5555
# Requirements

inject_slow_query.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55

66
require 'logger'
77
require 'redis'
8+
require_relative 'lib/slowlog_check'
89

910
LOGGER = Logger.new($stdout)
1011
LOGGER.level = Logger::WARN
1112

12-
REDIS = Redis.new(
13-
host: ENV.fetch('REDIS_HOST'),
14-
ssl: :true
15-
)
13+
REDIS = SlowlogCheck::Redis.new(
14+
host: ENV.fetch('REDIS_HOST')
15+
).redis
1616

1717
if ARGV[0].nil?
1818
raise "Specify milliseconds to inject as the first positional argument to `#{__FILE__}`"

lambda_function.rb

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33

44
require 'logger'
55
require 'date'
6-
require 'redis'
76
require 'dogapi'
87
require_relative 'lib/slowlog_check'
98

109
LOGGER = Logger.new($stdout)
1110
LOGGER.level = Logger::INFO
12-
LOGGER.freeze
1311

1412
def event_time
1513
# DateTime because Time does not natively parse AWS CloudWatch Event time
@@ -24,6 +22,7 @@ def log_context
2422
LOGGER.info "Event time: #{event_time}."
2523
end
2624

25+
2726
def lambda_handler(event: {}, context: {})
2827
@event = event
2928
log_context
@@ -49,10 +48,9 @@ def lambda_handler(event: {}, context: {})
4948
ENV.fetch('DATADOG_API_KEY'),
5049
ENV.fetch('DATADOG_APP_KEY')
5150
),
52-
redis: Redis.new(
53-
host: ENV.fetch('REDIS_HOST'),
54-
ssl: :true
55-
),
51+
redis: {
52+
host: ENV.fetch('REDIS_HOST')
53+
},
5654
namespace: ENV.fetch('NAMESPACE'),
5755
env: ENV.fetch('ENV'),
5856
metricname: ENV.fetch('METRICNAME', 'elasticache.slowlog')

lib/slowlog_check.rb

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,18 @@
22
require 'logger'
33

44
class SlowlogCheck
5+
require_relative 'slowlog_check/redis'
6+
57
::LOGGER ||= ::Logger.new($stdout)
6-
MAXLENGTH = 1048576 #255 levels of recursion for #
78

89
def initialize(params = {})
910
@ddog = params.fetch(:ddog)
10-
@redis = params.fetch(:redis)
11+
@redis= SlowlogCheck::Redis.new(params.fetch(:redis))
1112
@metricname = params.fetch(:metricname)
1213
@namespace = params.fetch(:namespace)
1314
@env = params.fetch(:env)
1415
end
1516

16-
def replication_group
17-
host = @redis.connection.fetch(:host)
18-
matches = /\w\.(?<replication_group>[\w-]+)\.\w+\.\w+\.cache\.amazonaws\.com/.match(host)
19-
if matches
20-
matches[:replication_group]
21-
else
22-
raise "Unable to parse REDIS_HOST. Is #{host} a valid elasticache endpoint?"
23-
end
24-
end
25-
2617
def status_or_error(resp)
2718
return resp[1].fetch("status") if resp[1].key?("status")
2819
return resp[1].fetch("errors") if resp[1].key?("errors")
@@ -31,7 +22,7 @@ def status_or_error(resp)
3122

3223
def last_datadog_metrics_submitted_by_me_in_the_last_2_hours
3324
resp = @ddog.get_points(
34-
"#{@metricname}.95percentile{replication_group:#{replication_group}}",
25+
"#{@metricname}.95percentile{replication_group:#{@redis.replication_group}}",
3526
Time.now - 7200,
3627
Time.now
3728
)
@@ -114,17 +105,6 @@ def add_metric_to_bucket(prior, new)
114105
}
115106
end
116107

117-
def did_i_get_it_all?(slowlog)
118-
slowlog[-1][0] == 0
119-
end
120-
121-
def redis_slowlog(length = 128)
122-
resp = @redis.slowlog('get', length)
123-
124-
return resp if length > MAXLENGTH
125-
return resp if did_i_get_it_all?(resp)
126-
return redis_slowlog(length * 2)
127-
end
128108

129109
def empty_values
130110
{
@@ -169,7 +149,7 @@ def pad_results_with_zero(report)
169149

170150
def slowlogs_by_flush_interval
171151
result = reporting_interval
172-
redis_slowlog.each do |slowlog|
152+
@redis.slowlog.each do |slowlog|
173153
time = slowlog_time(slowlog)
174154
break if minute_precision(time) <= minute_precision(last_time_submitted)
175155

@@ -211,8 +191,8 @@ def slowlogs_by_flush_interval
211191

212192
def default_tags
213193
{
214-
replication_group: replication_group,
215-
service: replication_group,
194+
replication_group: @redis.replication_group,
195+
service: @redis.replication_group,
216196
namespace: @namespace,
217197
aws: 'true',
218198
env: @env
@@ -224,7 +204,7 @@ def emit_point(params)
224204
type = params.fetch(:type, 'gauge')
225205
interval = params.fetch(:interval, 60)
226206
points = params.fetch(:points)
227-
host = params.fetch(:host, replication_group)
207+
host = params.fetch(:host, @redis.replication_group)
228208
tags = params.fetch(:tags, default_tags)
229209

230210
LOGGER.info "Sending slowlog entry: #{metric}: #{points.first[1]}µs executing #{tags[:command]} at #{points.first[0]}."
@@ -238,7 +218,7 @@ def emit_point(params)
238218
tags: tags
239219
}
240220
)
241-
raise "Error submitting metric for #{replication_group}" unless status_or_error(resp) == "ok"
221+
raise "Error submitting metric for #{@redis.replication_group}" unless status_or_error(resp) == "ok"
242222

243223
# Sigh. After doing all the work to pass around Time objects, dogapi-rb changes this to an integer.
244224
@last_time_submitted = Time.at(points.first[0])

lib/slowlog_check/redis.rb

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
require 'redis'
2+
require 'uri'
3+
4+
class SlowlogCheck::Redis
5+
MAXLENGTH = 1048576 #255 levels of recursion for #
6+
7+
def initialize(opts)
8+
@host = opts[:host]
9+
end
10+
11+
def params
12+
if cluster_mode_enabled?
13+
{
14+
cluster: [uri],
15+
port: port,
16+
ssl: tls_mode?
17+
}
18+
else
19+
{
20+
host: hostname,
21+
port: port,
22+
ssl: tls_mode?
23+
}
24+
end
25+
end
26+
27+
def redis
28+
@redis ||= Redis.new(params)
29+
end
30+
31+
def replication_group
32+
if tls_mode?
33+
matches[:second]
34+
else
35+
matches[:first]
36+
end
37+
end
38+
39+
40+
def slowlog(length = 128)
41+
resp = redis.slowlog('get', length)
42+
43+
return resp if length > MAXLENGTH
44+
return resp if did_i_get_it_all?(resp)
45+
return slowlog(length * 2)
46+
end
47+
48+
private
49+
50+
def cluster_mode_enabled?
51+
if tls_mode?
52+
matches[:first] == 'clustercfg'
53+
else
54+
matches[:third] == ''
55+
end
56+
end
57+
58+
def did_i_get_it_all?(slowlog)
59+
slowlog[-1][0] == 0
60+
end
61+
62+
def hostname
63+
URI.parse(@host).hostname or
64+
@host
65+
end
66+
67+
def matches
68+
redis_uri_regex.match(@host)
69+
end
70+
71+
def port
72+
regex_port = matches[:port].to_i
73+
if regex_port > 0
74+
regex_port
75+
else
76+
6379
77+
end
78+
end
79+
80+
def uri
81+
'redis' +
82+
lambda { tls_mode? ? 's' : '' }.call +
83+
'://' +
84+
hostname +
85+
':' +
86+
port.to_s
87+
end
88+
89+
def redis_uri_regex
90+
/((?<scheme>redi[s]+)\:\/\/){0,1}(?<first>[0-9A-Za-z_-]+)\.(?<second>[0-9A-Za-z_-]+)\.{0,1}(?<third>[0-9A-Za-z_]*)\.(?<region>[0-9A-Za-z_-]+)\.cache\.amazonaws\.com:{0,1}(?<port>[0-9]*)/
91+
end
92+
93+
def tls_mode?
94+
matches[:scheme] == 'rediss' or
95+
%w[master clustercfg].include?(matches[:first])
96+
end
97+
98+
end

0 commit comments

Comments
 (0)