Last active
July 30, 2018 09:07
-
-
Save alg/6cf0d0309185c3cdb40deffbde6e8220 to your computer and use it in GitHub Desktop.
ETS 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
defmodule Metrics do | |
use GenServer | |
@type metric_pid :: pid() | |
@type server :: term() | |
@type time_tag :: :daily | :hourly | :minutely | |
@type metric :: {term(), term()} | |
@type server_metric :: {:metric, {server(), time_tag(), term()}, metric()} | |
@spec start_link() :: metric_pid() | |
def start_link() do | |
GenServer.start_link(__MODULE__, []) | |
end | |
@doc """ | |
Writes a timed metric for the given server and time. Creates the number of time-tagged records for | |
daily, hourly and minutely metrics. Overwrites previous values for seamless round-robin. | |
""" | |
@spec write(metric_pid(), server(), DateTime.t(), {term(), term()}) :: :ok | |
def write(metrics, server, time, metric = {_, _}) do | |
GenServer.cast(metrics, {:write, server, time, metric}) | |
end | |
@doc """ | |
Reads time-tagged metrics for the given server. | |
""" | |
@spec read(metric_pid(), server(), time_tag()) :: [server_metric()] | |
def read(metrics, server, time_tag) do | |
GenServer.call(metrics, {:read, server, time_tag}) | |
end | |
@doc """ | |
Reads all metrics for the given server. | |
""" | |
@spec read(metric_pid(), server()) :: [server_metric()] | |
def read(metrics, server) do | |
GenServer.call(metrics, {:read, server}) | |
end | |
@doc """ | |
Prints statistics for the metrics storage. | |
""" | |
@spec info(metric_pid()) :: :ok | |
def info(metrics) do | |
GenServer.call(metrics, :info) | |
end | |
def init(_) do | |
db = :ets.new(:metrics, [:set, {:keypos, 2}]) | |
{:ok, db} | |
end | |
def handle_call(:info, _from, db) do | |
IO.inspect :ets.info(db) | |
{:reply, :ok, db} | |
end | |
def handle_call({:read, server}, _from, db) do | |
matches = | |
:ets.match_object(db, {:metric, {server, :_, :_}, :_}) | |
{:reply, matches, db} | |
end | |
def handle_call({:read, server, time_tag}, _from, db) do | |
matches = | |
:ets.match_object(db, {:metric, {server, time_tag, :_}, :_}) | |
{:reply, matches, db} | |
end | |
def handle_cast({:write, server, time, metric = {_, _}}, db) do | |
now = Timex.now() | |
if Timex.diff(now, time, :days) < 31 do | |
h = if Timex.diff(now, time, :hours) < 24 do | |
m = if Timex.diff(now, time, :minutes) < 60 do | |
[{:metric, {server, :minutely, time.minute}, metric}] | |
else | |
[] | |
end | |
[{:metric, {server, :hourly, time.hour}, metric}] ++ m | |
else | |
[] | |
end | |
recs = [{:metric, {server, :daily, time.day}, metric}] ++ h | |
:ets.insert(db, recs) | |
end | |
{:noreply, db} | |
end | |
end | |
defmodule MetricsTest do | |
use ExUnit.Case | |
alias Timex.Duration, as: D | |
setup do | |
{:ok, m} = GenServer.start_link(Metrics, []) | |
{:ok, metrics: m} | |
end | |
test "Storing metrics", %{metrics: m} do | |
met = {:foo, 1} | |
t = Timex.now() | |
Metrics.write(m, 123, t, met) | |
assert Enum.sort(Metrics.read(m, 123)) == [ | |
{:metric, {123, :daily, t.day}, met}, | |
{:metric, {123, :hourly, t.hour}, met}, | |
{:metric, {123, :minutely, t.minute}, met} | |
] | |
assert Metrics.read(m, 111) == [] | |
end | |
test "Overwriting metrics", %{metrics: m} do | |
now = Timex.now() | |
t = Timex.subtract(now, D.from_minutes(1)) | |
Metrics.write(m, 123, t, {:foo, :bar}) | |
Metrics.write(m, 123, now, {:foo, :baz}) | |
assert Enum.sort(Metrics.read(m, 123)) == [ | |
{:metric, {123, :daily, now.day}, {:foo, :baz}}, | |
{:metric, {123, :hourly, now.hour}, {:foo, :baz}}, | |
{:metric, {123, :minutely, t.minute}, {:foo, :bar}}, | |
{:metric, {123, :minutely, now.minute}, {:foo, :baz}} | |
] | |
assert Enum.sort(Metrics.read(m, 123, :minutely)) == [ | |
{:metric, {123, :minutely, t.minute}, {:foo, :bar}}, | |
{:metric, {123, :minutely, now.minute}, {:foo, :baz}} | |
] | |
end | |
test "Not storing out-of-scope minute metrics", %{metrics: m} do | |
now = Timex.now() | |
time = Timex.subtract(now, D.from_minutes(61)) | |
metric = {:foo, :bar} | |
Metrics.write(m, :sid, time, metric) | |
assert Enum.sort(Metrics.read(m, :sid)) == [ | |
{:metric, {:sid, :daily, time.day}, metric}, | |
{:metric, {:sid, :hourly, time.hour}, metric} | |
] | |
end | |
test "Not storing out-of-scope hour metrics", %{metrics: m} do | |
now = Timex.now() | |
time = Timex.subtract(now, D.from_minutes(24 * 60 + 1)) | |
metric = {:foo, :bar} | |
Metrics.write(m, :sid, time, metric) | |
assert Metrics.read(m, :sid) == [ | |
{:metric, {:sid, :daily, time.day}, metric} | |
] | |
end | |
test "Not storing out-of-scope day metrics", %{metrics: m} do | |
now = Timex.now() | |
time = Timex.subtract(now, D.from_days(32)) | |
metric = {:foo, :bar} | |
Metrics.write(m, :sid, time, metric) | |
assert Metrics.read(m, :sid) == [] | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment