Ferricstore.Stats (ferricstore v0.3.2)

Copy Markdown View Source

Tracks global server statistics using :counters for lock-free atomic increments.

Maintains the following counters:

  • total_connections_received — number of TCP connections accepted since startup
  • total_commands_processed — number of commands dispatched since startup
  • hot_reads — number of reads served from the ETS hot cache
  • cold_reads — number of reads that fell through to Bitcask on disk

Also stores the server start time and a random run ID (hex string) generated at startup.

Hot/cold read tracking

Every read through Router.get/1 is classified as either hot (served from ETS) or cold (required a Bitcask disk read). Counts are tracked at two levels:

  1. Global — via atomic counters (slots 3 and 4), used for INFO stats.
  2. Per-prefix — via the :ferricstore_hotness ETS table, used by the FERRICSTORE.HOTNESS command. The prefix is the first colon-delimited component of the key, or "_root" when no colon is present.

The per-prefix table is capped at @max_tracked_prefixes (1000). Once the cap is reached, new prefixes that would exceed it are bucketed under the "_other" pseudo-prefix to bound memory usage.

Architecture

Uses a single :counters reference with four slots for the hot-path counters. The start time and run ID are stored via :persistent_term for zero-cost reads from any process. The hotness ETS table is a public :set table allowing concurrent writers from any process.

Usage

Ferricstore.Stats.incr_connections()
Ferricstore.Stats.incr_commands()
Ferricstore.Stats.record_hot_read("user:42")
Ferricstore.Stats.record_cold_read("user:42")
Ferricstore.Stats.total_hot_reads()
Ferricstore.Stats.total_cold_reads()
Ferricstore.Stats.hotness_top(5)

Summary

Types

A prefix hotness entry: {prefix, hot_count, cold_count, cold_pct}.

Functions

Returns the current number of active connections.

Returns a specification to start this module under a supervisor.

Returns approximate cold reads per second since server startup.

Decrements the active connection counter by 1.

Decrements the keys_with_expiry counter (key lost TTL or was deleted).

Returns evicted keys count.

Returns evicted keys count using instance ctx.

Returns expired keys count.

Returns expired keys count using instance ctx.

Extracts the prefix from a key by splitting on the first colon.

Returns the hot read percentage as a float between 0.0 and 100.0.

Returns the top n prefixes sorted by cold read count (descending).

Increments the total commands processed counter by 1.

Increments the total commands processed counter using a cached counter ref.

Increments the total commands processed counter by N using a cached counter ref.

Increments the total connections received counter by 1 and the active connection counter by 1.

Increments the evicted_keys counter by count.

Increments the evicted_keys counter using instance ctx.

Increments the expired_keys counter by count.

Increments the expired_keys counter using instance ctx.

Increments the keys_with_expiry counter (key gained a TTL).

Increments the keyspace_hits counter by 1.

Increments the keyspace_hits counter using instance ctx.

Increments the keyspace_misses counter by 1.

Increments the keyspace_misses counter using instance ctx.

Returns the number of keys that currently have a TTL set.

Returns the total number of successful key lookups since startup.

Returns keyspace hits using instance ctx.

Returns the total number of failed key lookups since startup.

Returns keyspace misses using instance ctx.

Records a cold read (Bitcask disk fallback) for the given key.

Records a cold read using instance ctx.

Records a hot read (ETS cache hit) for the given key.

Records a hot read using instance ctx.

Resets all Stats counters to zero: connections, commands, hot reads, and cold reads.

Resets all hotness counters (both global and per-prefix).

Returns the random hex run ID generated at startup.

Starts the Stats process and initialises counters.

Returns the server start time as a monotonic millisecond timestamp.

Returns the total number of cold reads (Bitcask fallbacks) since startup.

Returns the total number of commands processed since startup.

Returns the total number of connections received since startup.

Returns the total number of hot reads (ETS cache hits) since startup.

Returns the server uptime in seconds.

Types

hotness_entry()

@type hotness_entry() :: {binary(), non_neg_integer(), non_neg_integer(), float()}

A prefix hotness entry: {prefix, hot_count, cold_count, cold_pct}.

Functions

active_connections()

@spec active_connections() :: non_neg_integer()

Returns the current number of active connections.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

cold_reads_per_second()

@spec cold_reads_per_second() :: float()

Returns approximate cold reads per second since server startup.

Uses uptime_seconds/0 as the denominator. Returns 0.0 when uptime is 0.

decr_connections()

@spec decr_connections() :: :ok

Decrements the active connection counter by 1.

Called when a connection closes. The total_connections counter is not decremented (it tracks lifetime connections accepted).

decr_keys_with_expiry()

@spec decr_keys_with_expiry() :: :ok

Decrements the keys_with_expiry counter (key lost TTL or was deleted).

evicted_keys()

@spec evicted_keys() :: non_neg_integer()

Returns evicted keys count.

evicted_keys(ctx)

@spec evicted_keys(FerricStore.Instance.t()) :: non_neg_integer()

Returns evicted keys count using instance ctx.

expired_keys()

@spec expired_keys() :: non_neg_integer()

Returns expired keys count.

expired_keys(ctx)

@spec expired_keys(FerricStore.Instance.t()) :: non_neg_integer()

Returns expired keys count using instance ctx.

extract_prefix(key)

@spec extract_prefix(binary()) :: binary()

Extracts the prefix from a key by splitting on the first colon.

Returns the portion before the first ":", or "_root" when the key contains no colon.

Examples

iex> Ferricstore.Stats.extract_prefix("user:42")
"user"

iex> Ferricstore.Stats.extract_prefix("plain_key")
"_root"

iex> Ferricstore.Stats.extract_prefix("a:b:c")
"a"

hot_read_pct()

@spec hot_read_pct() :: float()

Returns the hot read percentage as a float between 0.0 and 100.0.

Returns 0.0 when no reads have been recorded.

hotness_top(n \\ 10)

@spec hotness_top(pos_integer()) :: [hotness_entry()]

Returns the top n prefixes sorted by cold read count (descending).

Each entry is a tuple {prefix, hot_count, cold_count, cold_pct} where cold_pct is the percentage of reads for that prefix that were cold.

Parameters

  • n — maximum number of entries to return (default: 10)

Examples

iex> Ferricstore.Stats.hotness_top(5)
[{"user", 1000, 50, 4.76}, {"session", 500, 200, 28.57}]

incr_commands()

@spec incr_commands() :: :ok

Increments the total commands processed counter by 1.

incr_commands(counter_ref)

@spec incr_commands(reference()) :: :ok

Increments the total commands processed counter using a cached counter ref.

incr_commands_by(counter_ref, n)

@spec incr_commands_by(reference(), pos_integer()) :: :ok

Increments the total commands processed counter by N using a cached counter ref.

incr_connections()

@spec incr_connections() :: :ok

Increments the total connections received counter by 1 and the active connection counter by 1.

Also checks the current active connection count against maxclients and emits a [:ferricstore, :connection, :threshold] telemetry event when the count crosses the 80% or 95% thresholds.

incr_evicted_keys(count)

@spec incr_evicted_keys(non_neg_integer()) :: :ok

Increments the evicted_keys counter by count.

incr_evicted_keys(ctx, count)

@spec incr_evicted_keys(FerricStore.Instance.t(), non_neg_integer()) :: :ok

Increments the evicted_keys counter using instance ctx.

incr_expired_keys(count)

@spec incr_expired_keys(non_neg_integer()) :: :ok

Increments the expired_keys counter by count.

incr_expired_keys(ctx, count)

@spec incr_expired_keys(FerricStore.Instance.t(), non_neg_integer()) :: :ok

Increments the expired_keys counter using instance ctx.

incr_keys_with_expiry()

@spec incr_keys_with_expiry() :: :ok

Increments the keys_with_expiry counter (key gained a TTL).

incr_keyspace_hits()

@spec incr_keyspace_hits() :: :ok

Increments the keyspace_hits counter by 1.

incr_keyspace_hits(ctx)

@spec incr_keyspace_hits(FerricStore.Instance.t()) :: :ok

Increments the keyspace_hits counter using instance ctx.

incr_keyspace_misses()

@spec incr_keyspace_misses() :: :ok

Increments the keyspace_misses counter by 1.

incr_keyspace_misses(ctx)

@spec incr_keyspace_misses(FerricStore.Instance.t()) :: :ok

Increments the keyspace_misses counter using instance ctx.

keys_with_expiry()

@spec keys_with_expiry() :: non_neg_integer()

Returns the number of keys that currently have a TTL set.

keyspace_hits()

@spec keyspace_hits() :: non_neg_integer()

Returns the total number of successful key lookups since startup.

keyspace_hits(ctx)

@spec keyspace_hits(FerricStore.Instance.t()) :: non_neg_integer()

Returns keyspace hits using instance ctx.

keyspace_misses()

@spec keyspace_misses() :: non_neg_integer()

Returns the total number of failed key lookups since startup.

keyspace_misses(ctx)

@spec keyspace_misses(FerricStore.Instance.t()) :: non_neg_integer()

Returns keyspace misses using instance ctx.

record_cold_read(key)

@spec record_cold_read(binary()) :: :ok

Records a cold read (Bitcask disk fallback) for the given key.

Increments both the global cold-read counter and the per-prefix cold counter in the hotness ETS table.

Parameters

  • key — the key that required a Bitcask read

record_cold_read(ctx, key)

@spec record_cold_read(FerricStore.Instance.t(), binary()) :: :ok

Records a cold read using instance ctx.

record_hot_read(key)

@spec record_hot_read(binary()) :: :ok

Records a hot read (ETS cache hit) for the given key.

Increments both the global hot-read counter and the per-prefix hot counter in the hotness ETS table.

Parameters

  • key — the key that was read from ETS

record_hot_read(ctx, key)

@spec record_hot_read(FerricStore.Instance.t(), binary()) :: :ok

Records a hot read using instance ctx.

reset()

@spec reset() :: :ok

Resets all Stats counters to zero: connections, commands, hot reads, and cold reads.

Also clears the per-prefix hotness table. The run ID and start time are not reset.

Used by CONFIG RESETSTAT to clear accumulated statistics.

reset_hotness()

@spec reset_hotness() :: :ok

Resets all hotness counters (both global and per-prefix).

Useful for tests and for the FERRICSTORE.HOTNESS RESET subcommand.

run_id()

@spec run_id() :: binary()

Returns the random hex run ID generated at startup.

start_link(opts \\ [])

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

Starts the Stats process and initialises counters.

start_time()

@spec start_time() :: integer()

Returns the server start time as a monotonic millisecond timestamp.

total_cold_reads()

@spec total_cold_reads() :: non_neg_integer()

Returns the total number of cold reads (Bitcask fallbacks) since startup.

total_cold_reads(ctx)

@spec total_cold_reads(FerricStore.Instance.t()) :: non_neg_integer()

total_commands()

@spec total_commands() :: non_neg_integer()

Returns the total number of commands processed since startup.

total_connections()

@spec total_connections() :: non_neg_integer()

Returns the total number of connections received since startup.

total_hot_reads()

@spec total_hot_reads() :: non_neg_integer()

Returns the total number of hot reads (ETS cache hits) since startup.

total_hot_reads(ctx)

@spec total_hot_reads(FerricStore.Instance.t()) :: non_neg_integer()

uptime_seconds()

@spec uptime_seconds() :: non_neg_integer()

Returns the server uptime in seconds.