Skip to content

Commit

Permalink
Merge pull request #76 from aerosol/emit-telemetry-events-for-hit-and…
Browse files Browse the repository at this point in the history
…-miss

Emit telemetry events for hit and miss
  • Loading branch information
sasa1977 authored Apr 8, 2024
2 parents 09b98ed + 3ccb5d1 commit 104362d
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 25 deletions.
10 changes: 8 additions & 2 deletions lib/con_cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ defmodule ConCache do
:acquire_lock_timeout,
:callback,
:touch_on_read,
:lock_pids
:lock_pids,
:name
]

@type t :: pid | atom | {:global, any} | {:via, atom, any}
Expand Down Expand Up @@ -223,7 +224,12 @@ defmodule ConCache do
`touch_on_read` option is set while starting the cache.
"""
@spec get(t, key) :: value
def get(cache_id, key), do: Operations.get(Owner.cache(cache_id), key)
def get(cache_id, key) do
case Operations.fetch(Owner.cache(cache_id), key) do
:error -> nil
{:ok, value} -> value
end
end

@doc """
Stores the item into the cache.
Expand Down
58 changes: 38 additions & 20 deletions lib/con_cache/operations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,14 @@ defmodule ConCache.Operations do
defp lock_pid(cache, key),
do: elem(cache.lock_pids, :erlang.phash2(key, tuple_size(cache.lock_pids)))

def get(cache, key) do
case fetch(cache, key) do
{:ok, value} -> value
:error -> nil
end
end

defp fetch(%ConCache{ets: ets} = cache, key) do
def fetch(%ConCache{ets: ets} = cache, key, opts \\ []) do
case :ets.lookup(ets, key) do
[] ->
emit(telemetry_miss(), cache, opts)
:error

[{^key, value}] ->
emit(telemetry_hit(), cache, opts)
read_touch(cache, key)
{:ok, value}

Expand All @@ -42,6 +37,7 @@ defmodule ConCache.Operations do
|> Enum.map(fn {^key, value} -> value end)

read_touch(cache, key)
emit(telemetry_hit(), cache, opts)
{:ok, values}
end
end
Expand Down Expand Up @@ -177,9 +173,13 @@ defmodule ConCache.Operations do

def get_or_store(cache, key, fun) do
if valid_ets_type?(cache) do
case get(cache, key) do
nil -> isolated_get_or_store(cache, key, fun)
value -> value
case fetch(cache, key, emit_telemetry?: false) do
:error ->
isolated_get_or_store(cache, key, fun)

{:ok, value} ->
emit(telemetry_hit(), cache)
value
end
else
raise_ets_type(cache)
Expand All @@ -194,13 +194,14 @@ defmodule ConCache.Operations do

def dirty_get_or_store(cache, key, fun) do
if valid_ets_type?(cache) do
case get(cache, key) do
nil ->
case fetch(cache, key, emit_telemetry?: false) do
:error ->
new_value = fun.()
dirty_put(cache, key, new_value)
emit(telemetry_miss(), cache)
value(new_value)

existing ->
{:ok, existing} ->
existing
end
else
Expand All @@ -210,9 +211,11 @@ defmodule ConCache.Operations do

def fetch_or_store(cache, key, fun) do
if valid_ets_type?(cache) do
case fetch(cache, key) do
case fetch(cache, key, emit_telemetry?: false) do
:error -> isolated_fetch_or_store(cache, key, fun)
{:ok, existing} -> {:ok, existing}
{:ok, existing} ->
emit(telemetry_hit(), cache)
{:ok, existing}
end
else
raise_ets_type(cache)
Expand All @@ -227,11 +230,12 @@ defmodule ConCache.Operations do

def dirty_fetch_or_store(cache, key, fun) do
if valid_ets_type?(cache) do
case fetch(cache, key) do
case fetch(cache, key, emit_telemetry?: false) do
:error ->
case fun.() do
{:ok, new_value} ->
dirty_put(cache, key, new_value)
emit(telemetry_miss(), cache)
{:ok, value(new_value)}

{:error, _reason} = error ->
Expand Down Expand Up @@ -273,13 +277,27 @@ defmodule ConCache.Operations do
end

defp with_existing(cache, key, fun) do
case get(cache, key) do
nil -> nil
existing -> fun.(existing)
case fetch(cache, key) do
:error -> nil
{:ok, existing} -> fun.(existing)
end
end

def touch(cache, key) do
set_ttl(cache, key, :renew)
end

def telemetry_hit() do
[:con_cache, :stats, :hit]
end

def telemetry_miss() do
[:con_cache, :stats, :miss]
end

defp emit(event, cache, opts \\ []) do
if Keyword.get(opts, :emit_telemetry?, true) do
:telemetry.execute(event, %{}, %{cache: cache})
end
end
end
3 changes: 2 additions & 1 deletion lib/con_cache/owner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ defmodule ConCache.Owner do
acquire_lock_timeout: options[:acquire_lock_timeout] || 5000,
callback: options[:callback],
touch_on_read: options[:touch_on_read] || false,
lock_pids: List.to_tuple(ConCache.LockSupervisor.lock_pids(parent_process()))
lock_pids: List.to_tuple(ConCache.LockSupervisor.lock_pids(parent_process())),
name: options[:name]
}

{:ok, _} = Registry.register(ConCache, {parent_process(), __MODULE__}, cache)
Expand Down
5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ defmodule ConCache.Mixfile do

def application do
[
applications: [:logger],
applications: [:logger, :telemetry],
mod: {ConCache.Application, []}
]
end

defp deps do
[
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:dialyxir, "~> 1.0", only: :dev, runtime: false}
{:dialyxir, "~> 1.0", only: :dev, runtime: false},
{:telemetry, "~> 1.0"}
]
end

Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
}
80 changes: 80 additions & 0 deletions test/con_cache_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ defmodule ConCacheTest do
{:ok, _} = start_cache(name: name)
ConCache.put(name, :a, 1)
assert ConCache.get(name, :a) == 1
assert ConCache.Owner.cache(name).name == name
end
end

Expand Down Expand Up @@ -463,6 +464,85 @@ defmodule ConCacheTest do
assert ConCache.get(cache, :key) == :value
end

describe "telemetry" do
defmodule TestTelemetryHandler do
def handle_event(event, _measurments, metadata, _config) do
send(self(), {:telemetry_handled, event, metadata})
end
end

setup do
hit = ConCache.Operations.telemetry_hit()
miss = ConCache.Operations.telemetry_miss()

:telemetry.attach_many(
"test",
[
hit,
miss
],
&TestTelemetryHandler.handle_event/4,
nil
)

{:ok, hit_event: hit, miss_event: miss}
end

test "get/2 emits telemetry events", %{hit_event: hit, miss_event: miss, test: test} do
{:ok, cache} = ConCache.start_link(ttl_check_interval: false, name: test)

ConCache.get(cache, :key)
assert_receive {:telemetry_handled, ^miss, %{cache: %{owner_pid: ^cache, name: ^test}}}

ConCache.put(cache, :key, :value)
ConCache.get(cache, :key)
assert_receive {:telemetry_handled, ^hit, %{cache: %{owner_pid: ^cache, name: ^test}}}
refute_receive _
end

test "get_or_store/3 emits telemetry events", %{hit_event: hit, miss_event: miss} do
{:ok, cache} = ConCache.start_link(ttl_check_interval: false)

ConCache.get_or_store(cache, :key, fn -> :value end)
assert_receive {:telemetry_handled, ^miss, %{cache: %{owner_pid: ^cache, name: nil}}}
refute_receive _

ConCache.get_or_store(cache, :key, fn -> :value end)
assert_receive {:telemetry_handled, ^hit, %{cache: %{owner_pid: ^cache, name: nil}}}
refute_receive _
end

test "get_or_store/3 emits telemetry hit after put/3 is made", %{hit_event: hit, test: test} do
assert {:ok, cache} = ConCache.start_link(ttl_check_interval: false, name: test)

ConCache.put(cache, :key, fn -> :value end)
ConCache.get_or_store(cache, :key, fn -> :value end)
assert_receive {:telemetry_handled, ^hit, %{cache: %{owner_pid: ^cache, name: ^test}}}
refute_receive _
end

test "fetch_or_store/3 emits telemetry events", %{hit_event: hit, miss_event: miss} do
{:ok, cache} = ConCache.start_link(ttl_check_interval: false)

ConCache.fetch_or_store(cache, :key, fn -> {:ok, :value} end)
assert_receive {:telemetry_handled, ^miss, %{cache: %{owner_pid: ^cache, name: nil}}}
refute_receive _

ConCache.fetch_or_store(cache, :key, fn -> {:ok, :value} end)
assert_receive {:telemetry_handled, ^hit, %{cache: %{owner_pid: ^cache, name: nil}}}
refute_receive _
end

test "fetch_or_store/3 emits telemetry hit after put/3 is made", %{hit_event: hit, test: test} do
assert {:ok, cache} = ConCache.start_link(ttl_check_interval: false, name: test)

ConCache.put(cache, :key, fn -> {:ok, :value} end)
ConCache.get_or_store(cache, :key, fn -> {:ok, :value} end)
assert_receive {:telemetry_handled, ^hit, %{cache: %{owner_pid: ^cache, name: ^test}}}
refute_receive _
end
end

defp start_cache(opts \\ []) do
ConCache.start_link(Keyword.merge([ttl_check_interval: false], opts))
end
Expand Down

0 comments on commit 104362d

Please sign in to comment.