# Metrics API for Python

Every public class, function and type of the Metrics engine in `tinystore`, generated from its source. The [Metrics guide](https://tinyshed.org/tinystore/docs/guides/metrics) explains how to use them, and the [Bun and Node](https://tinyshed.org/tinystore/docs/reference/bun/metrics), [Go](https://tinyshed.org/tinystore/docs/reference/go/metrics) and [Rust](https://tinyshed.org/tinystore/docs/reference/rust/metrics) pages list the same API.

## Series

```python
@dataclass(slots=True)
class Series:
    name: str
    kind: SeriesKind
    labels: Mapping[str, LabelValue] = field(default_factory=dict[str, LabelValue])
    samples: Sequence[tuple[Time, float]] | None = None
    times: Sequence[Time] | None = None
    values: Sequence[float] | None = None
    keep: Duration | None = None  # how long the name's samples stay: its own, kept in the file; unless given, what it keeps already
```

A series: what ingest writes, and what a read gives back.

Ingest takes its samples as (time, value) pairs, or as times and values
columns, a time an aware datetime or unix milliseconds. A read gives
`times`, unix milliseconds in an `array('q')`, and `values`, an
`array('d')` whose bytes keep every value's bits: -0 and a NaN's
payload survive.

## AggregateBucket

```python
@dataclass(frozen=True, slots=True)
class AggregateBucket:
    at: datetime  # where it begins
    samples: int  # how many samples it comes from; a timer's, how many durations
    resets: int
    partial: bool  # its value comes from less than its span: the range, the clock or retention cut it
    gap: bool  # its first step began before the bucket before it: a whole bucket between had no samples
    value: float | None = None
    lower: float | None = None
    upper: float | None = None  # infinity past the last bound
    estimate: float | None = None
```

A bucket of an aggregate.

A bucket of count … last has its value; a percentile's is a gauge's sample
at the rank, or a timer's bucket of its spread that holds it, from lower
to upper, and with estimate asked a point between them.

## Aggregate

```python
@dataclass(frozen=True, slots=True)
class Aggregate:
    name: str
    labels: dict[str, str]  # the labels by kept or without left
    buckets: list[AggregateBucket]  # a bucket without samples is left out
```

## Point

```python
@dataclass(frozen=True, slots=True)
class Point:
    name: str
    labels: dict[str, str]
    at: datetime
    value: float
```

A series' newest sample in the range.

## Plan

```python
@dataclass(frozen=True, slots=True)
class Plan:
    series: int
    bytes: int
    decoded: int
    exceeds: Exceeds | None
```

What a read or an aggregate would spend of each bound, and the bound it would stop at.

## NameInfo

```python
@dataclass(frozen=True, slots=True)
class NameInfo:
    name: str
    kind: SeriesKind
    series_count: int
    unit: str
    help: str
    keep: str
    retained_from: datetime  # its oldest sample, or where its keep begins while it holds samples from before
    newest: datetime
```

A name the store holds samples of.

## NamePage

```python
@dataclass(frozen=True, slots=True)
class NamePage:
    names: list[NameInfo]
    next: str | None  # where the next page starts; none on the last
```

## MetricsDamage

```python
@dataclass(frozen=True, slots=True)
class MetricsDamage:
    name: str
    labels: dict[str, str]
    error: str
```

A series whose bytes no longer read, which delete removes.

## Metrics

```python
class Metrics:
    ...
```

A store's metrics: series by name and labels, their reads and aggregates, and the program's instruments.

### Metrics.ingest

```python
async def ingest(series: Series | Sequence[Series]) -> None: ...
```

Writes series and their samples, every one or none: a refused series names itself.

### Metrics.read

```python
async def read(**query: Unpack[SeriesQuery]) -> list[Series]: ...
```

The samples of each series the query finds, in its range: whole, or a LimitError naming its bound.

### Metrics.latest

```python
async def latest(**query: Unpack[SeriesQuery]) -> list[Point]: ...
```

The newest sample of each series the query finds, when it has one in the range.

### Metrics.aggregate

```python
async def aggregate(**query: Unpack[AggregateQuery]) -> list[Aggregate]: ...
```

The buckets of each group of the series the query finds, each one exact value of op, rounded once.

### Metrics.explain

```python
async def explain(**query: Unpack[AggregateQuery]) -> Plan: ...
```

What a read, or an aggregate when given its op, would spend of each bound; it decodes no sample.

### Metrics.names

```python
async def names(
    *,
    prefix: str | None = None,
    limit: int | None = None,
    after: str | None = None,
) -> NamePage: ...
```

A page of the names the store holds samples of, in the byte order of their text.

### Metrics.label_names

```python
async def label_names(
    name: str,
    *,
    prefix: str | None = None,
    limit: int | None = None,
    after: str | None = None,
) -> LabelPage: ...
```

A page of a name's label names, but the store's own.

### Metrics.label_values

```python
async def label_values(
    name: str,
    label: str,
    *,
    prefix: str | None = None,
    limit: int | None = None,
    after: str | None = None,
) -> ValuePage: ...
```

A page of the values a label of a name's series holds.

### Metrics.describe

```python
async def describe(name: str, *, unit: str = '', help: str = '') -> None: ...
```

Sets a name's unit and help in place of what it had: what is not given is removed.

They belong to the name, so a delete of its series leaves them.

### Metrics.description

```python
async def description(name: str) -> Description: ...
```

A name's unit and help; both empty when nothing described it.

### Metrics.delete

```python
async def delete(*, name: str, labels: Mapping[str, LabelValue]) -> bool: ...
```

Removes the one series of exactly this name and these labels, whether its samples still read or not.

True when the store held it; `labels={}` is the series without labels.

### Metrics.damaged

```python
async def damaged() -> list[MetricsDamage]: ...
```

The series whose bytes no longer read, 1000 at most, each with what broke; deleting them shows the next.

### Metrics.flush

```python
async def flush() -> None: ...
```

Sends what every instrument changed and has the store write its totals now, as it does every 15 seconds.

### Metrics.counter

```python
def counter(
    name: str,
    *,
    unit: str | None = None,
    help: str | None = None,
    keep: Duration | None = None,
) -> Counter: ...
```

A total that only goes up, which the store keeps: processes counting into one series count into one total.

### Metrics.gauge

```python
def gauge(
    name: str,
    *,
    unit: str | None = None,
    help: str | None = None,
    keep: Duration | None = None,
) -> Gauge: ...
```

A value that goes up and down: a queue's length, a temperature.

### Metrics.gauge_func

```python
def gauge_func(
    name: str,
    read: Callable[[], float | Awaitable[float]],
    *,
    labels: Mapping[str, LabelValue] | None = None,
    unit: str | None = None,
    help: str | None = None,
    keep: Duration | None = None,
) -> None: ...
```

A gauge whose value read gives every 15 seconds, of the set of labels given.

Another set of the name takes a function of its own. It may be async,
is never called again while a call of it runs, and an error or an
answer that is not a number writes nothing that time, said once while
it repeats:

```
store.metrics.gauge_func("queue_depth", emails.depth, labels={"queue": "emails"})
```

### Metrics.timer

```python
def timer(
    name: str,
    *,
    buckets: Sequence[float] | Literal['exponential'] | None = None,
    help: str | None = None,
    keep: Duration | None = None,
) -> Timer: ...
```

How many things took how long: their count, sum and longest in milliseconds, and their spread.

### Metrics.histogram

```python
def histogram(
    name: str,
    *,
    buckets: Sequence[float] | Literal['exponential'] | None = None,
    unit: str | None = None,
    help: str | None = None,
    keep: Duration | None = None,
) -> Histogram: ...
```

A timer of numbers that are not durations: a response's size in bytes.

## exponential_buckets

```python
def exponential_buckets(start: float, factor: float, count: int) -> list[float]: ...
```

Upper bounds from start, each factor times the one before: exponential_buckets(1, 2, 14) is 1, 2, 4 … 8192.

## Instruments

```python
class Instruments:
    ...
```

The instruments of a store's handle, and what sends their changes; `store.metrics` makes them.

### Instruments.declare

```python
def declare(
    name: str,
    form: Form,
    *,
    unit: str | None = None,
    help_: str | None = None,
    keep: Duration | None = None,
    buckets: Sequence[float] | Literal['exponential'] | None = None,
    read: bool = False,
) -> _Declared: ...
```

An instrument of name, made the first time; another form or other options than it has are refused.

### Instruments.reader

```python
def reader(
    name: str,
    labels: dict[str, str],
    read: Callable[[], float | Awaitable[float]],
    **told: Any,
) -> None: ...
```

A gauge of name whose set of labels read gives every 15 seconds; each set has a function of its own.

### Instruments.set_of

```python
def set_of(instrument: _Declared, labels: dict[str, str]) -> _Set: ...
```

The set of labels of an instrument, made the first time; past the sets it holds, its \_\_overflow.

### Instruments.pending

```python
def pending(set_: _Set) -> _Change | None: ...
```

### Instruments.change

```python
def change(set_: _Set) -> _Change: ...
```

What a call changes of a set, kept for the next batch.

### Instruments.flush

```python
async def flush() -> bool: ...
```

Sends what changed now and has the store write its totals; says whether there was anything to send.

One the store cannot be reached for fails as a call does, and what it
carried goes once the store can be. What the store refused since the
flush before, and what a store that started again may have lost, fail
it, each once.

### Instruments.stop

```python
async def stop() -> None: ...
```

Stops the timers, sends what is left, and ends the stream; the store's close calls it.

## Counter

```python
class Counter:
    ...
```

A total that only goes up, which the store keeps: processes counting into one series count into one total.

### Counter.labels

```python
def labels(**labels: LabelValue) -> Counter: ...
```

The same counter with more labels, a series of its own; the same labels in any order are one.

### Counter.inc

```python
def inc() -> None: ...
```

### Counter.add

```python
def add(n: float) -> None: ...
```

Adds n, a finite number of 0 or more; add(0) makes the set of labels known with a total of zero.

## Gauge

```python
class Gauge:
    ...
```

A value that goes up and down, written while this program is connected.

### Gauge.labels

```python
def labels(**labels: LabelValue) -> Gauge: ...
```

### Gauge.set

```python
def set(value: float) -> None: ...
```

### Gauge.add

```python
def add(n: float) -> None: ...
```

Moves it by n, from zero when it was never set.

### Gauge.inc

```python
def inc() -> None: ...
```

### Gauge.dec

```python
def dec() -> None: ...
```

## Timer

```python
class Timer:
    ...
```

How many things took how long: their count, sum and longest in milliseconds, and their spread.

### Timer.labels

```python
def labels(**labels: LabelValue) -> Timer: ...
```

### Timer.record

```python
def record(duration: timedelta | Duration) -> None: ...
```

Records a duration measured elsewhere: a timedelta, or text such as "250ms".

### Timer.measure

```python
def measure() -> Timing: ...
```

Measures the block it runs: `async with latency.measure():`, recorded whether it returns or raises.

### Timer.start

```python
def start() -> Timing: ...
```

A timing that records when it stops, for a span that is no one block.

## Timing

```python
class Timing:
    ...
```

A span a timer measures from its start; stop records it, once.

### Timing.stop

```python
def stop() -> timedelta: ...
```

Records how long since the timing started, the first time it is called, and gives it.

## Histogram

```python
class Histogram:
    ...
```

A timer of numbers that are not durations: a response's size in bytes.

### Histogram.labels

```python
def labels(**labels: LabelValue) -> Histogram: ...
```

### Histogram.record

```python
def record(value: float) -> None: ...
```

Records a value of 0 or more.
