Skip to content

Commit

Permalink
stats engine
Browse files Browse the repository at this point in the history
  • Loading branch information
dpep committed Oct 30, 2023
1 parent fdbad48 commit cedc673
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/network_resiliency.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "network_resiliency/stats"
require "network_resiliency/stats_engine"
require "network_resiliency/version"

module NetworkResiliency
Expand Down Expand Up @@ -103,6 +104,7 @@ def record(adapter:, action:, destination:, duration:, error: nil)
def reset
@enabled = nil
Thread.current["network_resiliency"] = nil
StatsEngine.reset
end

private
Expand Down
60 changes: 60 additions & 0 deletions lib/network_resiliency/stats_engine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module NetworkResiliency
module StatsEngine
extend self

LOCK = Thread::Mutex.new
STATS = {}

def add(key, value)
local, _ = synchronize do
STATS[key] ||= [ Stats.new, Stats.new ]
end

local << value
end

def get(key)
local, remote = synchronize { STATS[key] }

local && remote ? (local + remote) : Stats.new
end

def reset
synchronize { STATS.clear }
end

def sync(redis, keys)
data = synchronize do
keys.map do |key|
local, remote = STATS[key]
next unless local && remote
next unless local.n > 0

remote << local
STATS[key] = [ Stats.new, remote ]

[ key, local ]
end.compact.to_h
end

# sync data to redis
remote_stats = Stats.sync(redis, **data)

# integrate remote results
synchronize do
remote_stats.each do |key, stats|
local, remote = STATS[key]
STATS[key] = [ local, stats ]
end
end

remote_stats.keys
end

private

def synchronize
LOCK.synchronize { yield }
end
end
end
59 changes: 59 additions & 0 deletions spec/stats_engine_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
describe NetworkResiliency::StatsEngine do
let(:redis) { Redis.new }

describe ".add" do
it "accumulates stats" do
described_class.add("foo", 1)
end
end

describe ".get" do
it "returns accumulated stats" do
described_class.add("foo", 1)

res = described_class.get("foo")
expect(res).to be_a(NetworkResiliency::Stats)
expect(res).to approximate NetworkResiliency::Stats.new << 1
end

it "combines local and remote stats" do
described_class.add("foo", 1)
described_class.sync(redis, [ "foo" ])

described_class.add("foo", 3)

res = described_class.get("foo")
expect(res).to approximate NetworkResiliency::Stats.new << [ 1, 3 ]
end
end

describe ".sync" do
context "when there are remote stats" do
before do
described_class.add("foo", 1)
described_class.sync(redis, [ "foo" ])
described_class.reset
end

it "syncs stats to redis" do
described_class.add("foo", 1)
res = described_class.get("foo")
expect(res.n).to be 1

described_class.sync(redis, [ "foo" ])
res = described_class.get("foo")
expect(res.n).to be 2
end
end
end

describe ".reset" do
it "resets stats" do
described_class.add("foo", 1)
described_class.reset

res = described_class.get("foo")
expect(res.n).to be 0
end
end
end

0 comments on commit cedc673

Please sign in to comment.