CounterEx (CounterEx v0.2.0)

View Source

High-performance counter library with pluggable backends and namespace support.

CounterEx provides atomic counter operations with three backend options:

  • ETS (default): Dynamic counters, unlimited capacity
  • Atomics: Fixed capacity, ultra-fast operations
  • Counters: Fixed capacity, write-optimized

Quick Start

# Start the counter server (default ETS backend)
{:ok, _pid} = CounterEx.start_link()

# Increment counters
{:ok, 1} = CounterEx.inc(:my_counter)
{:ok, 2} = CounterEx.inc(:my_counter)

# Get counter value
{:ok, 2} = CounterEx.get(:my_counter)

# Use namespaces to organize counters
{:ok, 1} = CounterEx.inc(:metrics, :http_requests)
{:ok, 1} = CounterEx.inc(:metrics, :db_queries)

Backend Selection

# Use Atomics backend for maximum performance
{:ok, _pid} = CounterEx.start_link(
  backend: CounterEx.Backend.Atomics,
  backend_opts: [capacity: 10_000]
)

# Use Counters backend for write-heavy workloads
{:ok, _pid} = CounterEx.start_link(
  backend: CounterEx.Backend.Counters,
  backend_opts: [capacity: 5_000]
)

Namespaces

Counters can be organized into namespaces for logical grouping:

{:ok, _} = CounterEx.inc(:http, :requests)
{:ok, _} = CounterEx.inc(:http, :errors)
{:ok, _} = CounterEx.inc(:db, :queries)

# Get all counters in a namespace
{:ok, counters} = CounterEx.all(:http)
# => %{requests: 1, errors: 1}

Summary

Functions

Get all counters in a namespace.

Run benchmarks comparing backend performance.

Returns a child spec for use in supervision trees.

Delete all counters in a namespace.

Get the current value of a counter.

Increment a counter by the given step (default: 1).

Get information about the backend and counters.

Reset a counter to the initial value (default: 0).

Set a counter to a specific value.

Returns supervisor child spec for backward compatibility.

Returns supervisor child spec with sweep interval for backward compatibility.

Start the CounterEx server.

Functions

all(namespace \\ :default)

@spec all(CounterEx.Backend.namespace()) ::
  {:ok, %{required(CounterEx.Backend.key()) => integer()}} | {:error, term()}

Get all counters in a namespace.

Returns a map of counter keys to their values.

Examples

{:ok, _} = CounterEx.inc(:counter1)
{:ok, _} = CounterEx.inc(:counter2)
{:ok, counters} = CounterEx.all()
# => %{counter1: 1, counter2: 1}

# With specific namespace
{:ok, counters} = CounterEx.all(:metrics)

benchmark(opts \\ [])

Run benchmarks comparing backend performance.

Examples

CounterEx.benchmark()
CounterEx.benchmark(parallel: 4)

child_spec(opts)

Returns a child spec for use in supervision trees.

Examples

children = [
  {CounterEx, backend: CounterEx.Backend.ETS},
  # ... other children
]

Supervisor.start_link(children, strategy: :one_for_one)

compare_and_swap(key_or_namespace, expected_or_key, new_value_or_expected, new_value \\ nil)

@spec compare_and_swap(
  CounterEx.Backend.namespace(),
  CounterEx.Backend.key(),
  integer(),
  integer()
) ::
  {:ok, integer()} | {:error, :mismatch, integer()} | {:error, term()}

Compare-and-swap operation.

Atomically updates the counter to new_value only if its current value equals expected.

Returns {:ok, new_value} on success, or {:error, :mismatch, current_value} if the current value doesn't match the expected value.

Examples

{:ok, 10} = CounterEx.set(:my_counter, 10)

# CAS succeeds when value matches
{:ok, 20} = CounterEx.compare_and_swap(:my_counter, 10, 20)

# CAS fails when value doesn't match
{:error, :mismatch, 20} = CounterEx.compare_and_swap(:my_counter, 10, 30)

# With namespace
{:ok, 5} = CounterEx.compare_and_swap(:metrics, :requests, 0, 5)

delete(key_or_namespace, key \\ nil)

@spec delete(CounterEx.Backend.namespace(), CounterEx.Backend.key()) ::
  :ok | {:error, term()}

Delete a counter.

Examples

{:ok, _} = CounterEx.inc(:my_counter)
:ok = CounterEx.delete(:my_counter)
{:ok, nil} = CounterEx.get(:my_counter)

# With namespace
:ok = CounterEx.delete(:metrics, :requests)

delete_namespace(namespace)

@spec delete_namespace(CounterEx.Backend.namespace()) :: :ok | {:error, term()}

Delete all counters in a namespace.

Examples

:ok = CounterEx.delete_namespace(:metrics)

get(key_or_namespace, key \\ nil)

@spec get(CounterEx.Backend.namespace(), CounterEx.Backend.key()) ::
  {:ok, integer() | nil} | {:error, term()}

Get the current value of a counter.

Returns {:ok, value} if the counter exists, or {:ok, nil} if it doesn't.

Examples

{:ok, nil} = CounterEx.get(:my_counter)
{:ok, _} = CounterEx.inc(:my_counter)
{:ok, 1} = CounterEx.get(:my_counter)

# With namespace
{:ok, value} = CounterEx.get(:metrics, :requests)

inc(key_or_namespace, step_or_key \\ 1, default_or_step \\ 0, default \\ 0)

@spec inc(
  CounterEx.Backend.namespace(),
  CounterEx.Backend.key(),
  integer(),
  integer()
) ::
  {:ok, integer()} | {:error, term()}

Increment a counter by the given step (default: 1).

If the counter doesn't exist, it's initialized with default (default: 0) before incrementing.

Examples

{:ok, 1} = CounterEx.inc(:my_counter)
{:ok, 2} = CounterEx.inc(:my_counter)
{:ok, 12} = CounterEx.inc(:my_counter, 10)

# With namespace
{:ok, 1} = CounterEx.inc(:metrics, :requests)

# With default value
{:ok, 105} = CounterEx.inc(:counter, 5, 100)

info()

@spec info() :: {:ok, map()} | {:error, term()}

Get information about the backend and counters.

Returns a map with metadata including:

  • Backend type (:ets, :atomics, :counters)
  • Number of counters
  • Active namespaces
  • Backend-specific information

Examples

{:ok, info} = CounterEx.info()
info.type
# => :ets
info.counters_count
# => 42

reset(key_or_namespace, value_or_key \\ 0, value \\ 0)

@spec reset(CounterEx.Backend.namespace(), CounterEx.Backend.key(), integer()) ::
  {:ok, integer()} | {:error, term()}

Reset a counter to the initial value (default: 0).

Examples

{:ok, _} = CounterEx.inc(:my_counter)
{:ok, 0} = CounterEx.reset(:my_counter)

# Reset to custom value
{:ok, 10} = CounterEx.reset(:my_counter, 10)

# With namespace
{:ok, 0} = CounterEx.reset(:metrics, :requests)

set(key_or_namespace, value_or_key, value \\ nil)

@spec set(CounterEx.Backend.namespace(), CounterEx.Backend.key(), integer()) ::
  {:ok, integer()} | {:error, term()}

Set a counter to a specific value.

Examples

{:ok, 42} = CounterEx.set(:my_counter, 42)
{:ok, 42} = CounterEx.get(:my_counter)

# With namespace
{:ok, 100} = CounterEx.set(:metrics, :requests, 100)

start_keeper()

@spec start_keeper() :: {module(), keyword()}

Returns supervisor child spec for backward compatibility.

Examples

children = [
  CounterEx.start_keeper(),
  # ... other children
]

start_keeper_with_sweep(interval)

@spec start_keeper_with_sweep(integer()) ::
  {module(), keyword()} | {:error, :interval_is_not_integer}

Returns supervisor child spec with sweep interval for backward compatibility.

Examples

children = [
  CounterEx.start_keeper_with_sweep(60_000),
  # ... other children
]

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Start the CounterEx server.

Options

  • :backend - Backend module (default: CounterEx.Backend.ETS)
  • :backend_opts - Options for backend initialization
  • :interval - Sweep interval in milliseconds (clears all counters periodically)
  • :name - GenServer name (default: CounterEx.Keeper)

Examples

# Start with default ETS backend
{:ok, pid} = CounterEx.start_link()

# Start with Atomics backend
{:ok, pid} = CounterEx.start_link(
  backend: CounterEx.Backend.Atomics,
  backend_opts: [capacity: 10_000]
)