Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions lib/origin_simulator/counter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ defmodule OriginSimulator.Counter do

@initial_state %{total_requests: 0}

def start_link(_opts) do
Agent.start_link(fn -> @initial_state end, name: __MODULE__)
def start_link(opts) do
name = Keyword.get(opts, :name)
Agent.start_link(fn -> @initial_state end, name: if(name, do: name, else: __MODULE__))
end

def value do
Agent.get(__MODULE__, & &1)
def value(agent \\ __MODULE__) do
Agent.get(agent, & &1)
end

def clear do
Agent.update(__MODULE__, fn _state ->
def clear(agent \\ __MODULE__) do
Agent.update(agent, fn _state ->
@initial_state
end)
end

def increment(status_code) do
Agent.update(__MODULE__, fn state ->
def increment(status_code, agent \\ __MODULE__) do
Agent.update(agent, fn state ->
state
|> increment_key(:total_requests)
|> increment_key(status_code)
Expand Down
39 changes: 39 additions & 0 deletions lib/origin_simulator/rate_calculator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
defmodule OriginSimulator.RateCalculator do
use GenServer

alias OriginSimulator.Counter

def start_link(opts) do
name = Keyword.get(opts, :name)
GenServer.start_link(__MODULE__, opts, name: if(name, do: name, else: __MODULE__))
end

def rate(calculator \\ __MODULE__), do: GenServer.call(calculator, :rate)
def state(calculator \\ __MODULE__), do: GenServer.call(calculator, :state)

def current_count(counter \\ Counter), do: Counter.value(counter).total_requests

## Server Callbacks

@impl true
def init(opts) do
counter = Keyword.get(opts, :counter)
send(self(), {:calculate, if(counter, do: counter, else: Counter)})

{:ok, %{rate: 0, current_count: 0}}
end

@impl true
def handle_call(:rate, _from, state), do: {:reply, state.rate, state}

@impl true
def handle_call(:state, _from, state), do: {:reply, state, state}

@impl true
def handle_info({:calculate, counter}, state) do
Process.send_after(self(), {:calculate, counter}, 1000)

new_count = current_count(counter)
{:noreply, %{rate: new_count - state.current_count, current_count: new_count}}
end
end
3 changes: 2 additions & 1 deletion lib/origin_simulator/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ defmodule OriginSimulator.Supervisor do
children = [
OriginSimulator.Simulation,
OriginSimulator.Payload,
OriginSimulator.Counter
OriginSimulator.Counter,
OriginSimulator.RateCalculator
]

opts = [
Expand Down
34 changes: 34 additions & 0 deletions test/origin_simulator/counter_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule OriginSimulator.CounterTest do
use ExUnit.Case, async: true

alias OriginSimulator.Counter

setup_all do
{:ok, _} = start_supervised({Counter, name: TestCounter})
:ok
end

setup do
Counter.clear(TestCounter)
:ok
end

test "clear/1 resets total requests to zero initial state" do
assert Counter.value(TestCounter).total_requests == 0
end

test "value/1 returns the current count" do
assert Counter.value(TestCounter).total_requests == 0
end

test "increment/1 adds to the current count" do
Counter.increment(200, TestCounter)
Counter.increment(200, TestCounter)
assert Counter.value(TestCounter).total_requests == 2
end

test "increment/1 adds to the current count for the status" do
Counter.increment(404, TestCounter)
assert Counter.value(TestCounter)[404] == 1
end
end
41 changes: 41 additions & 0 deletions test/origin_simulator/rate_calculator_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule OriginSimulator.RateCalculatorTest do
use ExUnit.Case, async: true

alias OriginSimulator.{Counter, RateCalculator}

@test_counter TestCounterForRateCalculator

setup_all do
{:ok, _} = start_supervised({Counter, name: TestCounterForRateCalculator})
:ok
end

setup do
Counter.clear(@test_counter)
end

test "rate() returns a valid integer request per-second rate" do
assert RateCalculator.rate() |> is_integer()
end

test "state() returns the calculator state" do
assert RateCalculator.state() == %{current_count: 0, rate: 0}
end

test "current_count/1 returns the current request count" do
Counter.increment(200, @test_counter)
Counter.increment(200, @test_counter)
assert RateCalculator.current_count(@test_counter) == 2
end

test "handles request rate calculation" do
current_calculator_state = %{rate: 0, current_count: 0}

for _request <- 1..10 do
Counter.increment(200, @test_counter)
end

{:noreply, new_calculator_state} = RateCalculator.handle_info({:calculate, @test_counter}, current_calculator_state)
assert new_calculator_state.rate == 10
end
end