Skip to content

Commit

Permalink
mysql adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
dpep committed Oct 4, 2023
1 parent f484121 commit 470e106
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ GEM
ffi (~> 1.0)
msgpack (1.7.2)
multipart-post (2.3.0)
mysql2 (0.5.5)
rack (3.0.8)
rack-test (2.1.0)
rack (>= 1.3)
Expand Down Expand Up @@ -79,6 +80,7 @@ DEPENDENCIES
dogstatsd-ruby (<= 4.8.3)
faraday (~> 1)
faraday-rack
mysql2 (>= 0.5)
network_resiliency!
rack
rack-test
Expand Down
3 changes: 3 additions & 0 deletions lib/network_resiliency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Adapter
autoload :HTTP, "network_resiliency/adapter/http"
autoload :Faraday, "network_resiliency/adapter/faraday"
autoload :Redis, "network_resiliency/adapter/redis"
autoload :Mysql, "network_resiliency/adapter/mysql"
end

extend self
Expand All @@ -22,6 +23,8 @@ def patch(*adapters)
Adapter::HTTP.patch
when :redis
Adapter::Redis.patch
when :mysql
Adapter::Mysql.patch
else
raise NotImplementedError
end
Expand Down
52 changes: 52 additions & 0 deletions lib/network_resiliency/adapter/mysql.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
gem "mysql2", ">= 0.5"
require "mysql2"

module NetworkResiliency
module Adapter
module Mysql
extend self

def patch
return if patched?

Mysql2::Client.prepend(Instrumentation)
end

def patched?
Mysql2::Client.ancestors.include?(Instrumentation)
end

module Instrumentation
def connect(_, _, host, *args)
# timeout = query_options[:connect_timeout]

return super unless NetworkResiliency.enabled?(:mysql)

begin
ts = -NetworkResiliency.timestamp

super
rescue Mysql2::Error::TimeoutError => e
# capture error
raise
ensure
ts += NetworkResiliency.timestamp

NetworkResiliency.record(
adapter: "mysql",
action: "connect",
destination: host,
error: e&.class,
duration: ts,
)
end
end

# def query(sql, options = {})
# puts "query"
# super
# end
end
end
end
end
1 change: 1 addition & 0 deletions network_resiliency.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Gem::Specification.new do |s|
s.add_development_dependency "dogstatsd-ruby", "<= 4.8.3"
s.add_development_dependency "faraday", "~> 1"
s.add_development_dependency "faraday-rack"
s.add_development_dependency "mysql2", ">= 0.5"
s.add_development_dependency "rack"
s.add_development_dependency "rack-test"
s.add_development_dependency "redis", "~> 4"
Expand Down
74 changes: 74 additions & 0 deletions spec/mysql_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
describe NetworkResiliency::Adapter::Mysql, :mock_mysql do
before do
stub_const("Mysql2::Client", klass_mock)
end

let(:klass_mock) { Class.new(Mysql2::Client) }

describe ".patch" do
subject do
described_class.patched?
end

it { is_expected.to be false }

context "when patched" do
before do
allow(klass_mock).to receive(:prepend).and_call_original
described_class.patch
end

it { is_expected.to be true }

it "will only patch once" do
expect(klass_mock).to have_received(:prepend).once

described_class.patch

expect(klass_mock).to have_received(:prepend).once
end
end
end

describe ".connect" do
subject do
mysql rescue Mysql2::Error::ConnectionError

NetworkResiliency.statsd
end

let(:host) { "my.fav.sql.com" }
let(:mysql) { Mysql2::Client.new(host: host, socket: mock_mysql) }
# let(:select) { mysql.query("SELECT 1").first.first.last }

before do
described_class.patch
end

it "can not connect to a mysql server" do
expect { mysql }.to raise_error(Mysql2::Error::ConnectionError)
end

it "logs connection" do
is_expected.to have_received(:distribution).with(
/connect/,
Numeric,
anything,
)
end

it "logs duration" do
is_expected.to have_received(:distribution) do |_, duration, _|
expect(duration).to be > 0
end
end

it "tags the destination host" do
is_expected.to have_received(:distribution).with(
String,
Numeric,
tags: include(destination: host),
)
end
end
end
41 changes: 41 additions & 0 deletions spec/support/mock_mysql.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require "mysql2"
require "socket"

module Helpers
module MockMysql
SOCKET_PATH = "/tmp/mock_mysql.sock"

def mock_mysql
# raise Mysql2::Error::TimeoutError.new("fake timeout", nil, error_number = 1205)

File.delete(SOCKET_PATH) if File.exists?(SOCKET_PATH)

server = UNIXServer.new(SOCKET_PATH)

Thread.new do
socket, _ = server.accept

# line = socket.recv(1024)
# line = socket.gets.chomp
# puts "mysql server: #{line}"

# server_socket.write("#{resp}")
# server_socket.write("\r\n") unless resp.end_with?("\r\n")
ensure
socket&.close
end

SOCKET_PATH
end
end
end


RSpec.configure do |config|
config.include Helpers::MockMysql, :mock_mysql

config.after(mock_mysql: true) do
# clean up socket file
File.delete(Helpers::MockMysql::SOCKET_PATH) if File.exists?(Helpers::MockMysql::SOCKET_PATH)
end
end

0 comments on commit 470e106

Please sign in to comment.