# `Ankusa.Metrics.Cardinality`
[🔗](https://github.com/jamescarr/ankusa/blob/ankusa-v0.7.0/packages/ankusa/lib/ankusa/metrics/cardinality.ex#L1)

The series ceiling of `Ankusa.Metrics`: how many distinct label sets one
metric may keep before the rest are folded into `"other"`.

Only the *identity* tags, `:tenant_id`, `:source_id` and `:tenant`, are
policed. Every other label comes from a fixed set (an outcome, a sink module,
an HTTP status), and `metrics.detail` decides which identity tags a metric
carries at all. A metric's first `metrics.max_series_per_metric - 1` label
sets that include an identity are admitted and remembered; one slot is left
for the `"other"` series that every later label set is rewritten into
(`:tenant_id`, `:source_id` and `:tenant` each become `"other"`, the other
labels are kept, so a metric holds one `"other"` series per combination of
its fixed labels). A real tenant named `other` shares that series.

The admitted sets live in one public ETS table per instance, owned by this
process (a restart empties it, and `Ankusa.Metrics` restarts the reporter
with it, so the two never disagree). Admission runs in the process that
emitted the telemetry event, once per event and metric:

  * a known label set is a single `:ets.member/2`;
  * an unknown one reads the metric's counter, and once it is full returns
    without writing anything, so a flood of new tenants does not contend on
    the counter;
  * otherwise it reserves a slot (`:ets.update_counter/4`), claims the label
    set (`:ets.insert_new/2`) and gives the slot back when another process
    claimed the same set first. A reserved slot is only ever released, so the
    admitted sets never exceed the ceiling.

A series is never forgotten: `TelemetryMetricsPrometheus.Core` keeps one for
as long as the reporter runs, and so does this table.

# `admit`

```elixir
@spec admit(atom(), term(), [atom()], pos_integer(), map()) :: :admitted | :overflow
```

Admit `values`' label set into `name`'s series, or refuse it when `cap - 1`
sets are already held. `tags` are the metric's labels.

# `child_spec`

```elixir
@spec child_spec(keyword()) :: Supervisor.child_spec()
```

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `fold`

```elixir
@spec fold(atom(), term(), [atom()], pos_integer(), map()) :: map()
```

The label values to report for one event: `values` itself when its label set
is (or can become) one of `name`'s admitted series, else `values` with every
identity tag in `tags` replaced by `"other"`.

# `identity_tags`

```elixir
@spec identity_tags() :: [atom()]
```

The label names this policy bounds.

# `policed?`

```elixir
@spec policed?([atom()]) :: boolean()
```

Whether `tags` carry an identity label, i.e. whether a metric with them is policed.

# `start_link`

```elixir
@spec start_link(atom()) :: GenServer.on_start()
```

# `table`

```elixir
@spec table(atom()) :: atom()
```

Name of `instance`'s admitted-series table.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
