Packages

Elixir-native feature management for safe rollout, multivariate config, and explainable runtime decisions.

Current section

Files

Jump to
rulestead lib rulestead telemetry cache.ex
Raw

lib/rulestead/telemetry/cache.ex

defmodule Rulestead.Telemetry.Cache do
@moduledoc false
# ETS write-behind caching for telemetry.
# Stores `last_evaluated_at` and `variants_served` for each flag evaluated.
use GenServer
@table :rulestead_telemetry_cache
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def table_name, do: @table
@doc """
Record an evaluation.
Uses public ETS for high throughput.
"""
def record_evaluation(flag_key, environment_key, variant, timestamp) do
key = {flag_key, environment_key}
try do
:ets.insert(@table, {{key, :last_evaluated_at}, timestamp})
try do
:ets.update_counter(@table, {key, :variants_served, variant}, {2, 1})
rescue
ArgumentError ->
:ets.insert(@table, {{key, :variants_served, variant}, 1})
end
rescue
ArgumentError ->
# Table does not exist (likely in test environment where Cache is not started)
:ok
end
:ok
end
@doc """
Returns a snapshot of the cache.
"""
def snapshot do
:ets.tab2list(@table)
|> Enum.reduce(%{}, fn
{{{flag_key, env_key}, :last_evaluated_at}, timestamp}, acc ->
key = {flag_key, env_key}
map = Map.get(acc, key, %{})
Map.put(acc, key, Map.put(map, :last_evaluated_at, timestamp))
{{{flag_key, env_key}, :variants_served, variant}, count}, acc ->
key = {flag_key, env_key}
map = Map.get(acc, key, %{})
variants = Map.get(map, :variants_served, %{})
map = Map.put(map, :variants_served, Map.put(variants, variant, count))
Map.put(acc, key, map)
end)
end
@doc """
Clears the cache for flushing.
"""
def clear do
:ets.delete_all_objects(@table)
:ok
end
@impl true
def init(_opts) do
case :ets.info(@table) do
:undefined ->
:ets.new(@table, [
:named_table,
:public,
:set,
read_concurrency: true,
write_concurrency: true
])
_ ->
:ok
end
{:ok, %{}}
end
end