Metadata-Version: 2.4
Name: updownio-exporter
Version: 0.1.0
Summary: 
Requires-Python: >=3.13
Classifier: Development Status :: 5 - Production/Stable
Requires-Dist: daemon-metrics (>=1.0.1,<2.0.0)
Requires-Dist: prometheus-client (>=0.21.1)
Requires-Dist: requests (>=2.32.3)
Requires-Dist: the-conf (>=1.0.5)
Description-Content-Type: text/markdown

[![PyPI - Version](https://img.shields.io/pypi/v/updownio-exporter)](https://pypi.org/project/updownio-exporter/) [![Docker Image Version](https://img.shields.io/docker/v/jaesivsm/updownio-exporter)](https://hub.docker.com/r/jaesivsm/updownio-exporter/tags)

# updown.io Exporter

A Prometheus exporter for [updown.io](https://updown.io). It polls the updown.io
API for every check attached to one or more API keys and serves apdex,
response-time distribution, request counts, timings and SSL expiry on
`:9100/metrics`.

## Configuration

Configuration is read from, in order of precedence:

1. environment variables,
2. `/etc/updownio-exporter/updownio-exporter.json`,
3. `~/.config/updownio-exporter.json`.

```json
{
    "apikeys": ["ro-xxxxxxxxxxxxxxxxxxxx"],
    "loop": {"interval": 240, "reset": 168, "lookback": 24},
    "prometheus": {"port": 9100, "namespace": "updownio"},
    "logging": {"level": "WARNING"}
}
```

| Parameter             | Environment variable   | Default    | Meaning |
|-----------------------|------------------------|------------|---------|
| `apikeys`             | `APIKEYS_0`, `APIKEYS_1`, … | —     | updown.io API keys; a read-only (`ro-…`) key is enough |
| `loop.interval`       | `LOOP_INTERVAL`        | `240`      | seconds between two polls |
| `loop.reset`          | `LOOP_RESET`           | `168`      | hours before the observation window is restarted (see below) |
| `loop.lookback`       | `LOOP_LOOKBACK`        | `24`       | hours of history requested when the window (re)starts |
| `prometheus.port`     | `PROMETHEUS_PORT`      | `9100`     | listening port |
| `prometheus.namespace`| `PROMETHEUS_NAMESPACE` | `updownio` | prefix of the updown.io metrics |
| `logging.level`       | `LOGGING_LEVEL`        | `WARNING`  | `DEBUG`, `INFO`, `WARNING`, `ERROR` or `FATAL` |

`lookback` has to be long enough for updown.io to have statistics to serve: a
check running every 30 minutes needs about 24 hours.

## Running it

### ... with python:

```shell
pip install updownio-exporter
python -m updownio_exporter
```

### ... with docker:

```shell
docker run -e APIKEYS_0=ro-xxxxxxxxxxxxxxxxxxxx -p 9100:9100 jaesivsm/updownio-exporter
```

or with a configuration file:

```shell
docker run -v ~/.config/updownio-exporter.json:/etc/updownio-exporter/updownio-exporter.json:ro \
    -p 9100:9100 jaesivsm/updownio-exporter
```

## Metrics served

Every check is labelled by its `alias`, lowercased and with spaces replaced by
dashes (`My Blog` becomes `my-blog`), or by its token when it has no alias.
Disabled checks are skipped: updown.io serves no statistics for them. Enabled
checks show up once updown.io has at least an hour of samples for them.

| Metric                   | Type      | Labels           | Meaning |
|--------------------------|-----------|------------------|---------|
| `updownio_apdex`         | gauge     | `alias`          | apdex over the observation window |
| `updownio_timings`       | gauge     | `alias`, `step`  | average duration of each request step, in ms (`redirect`, `namelookup`, `connection`, `handshake`, `response`, `total`) |
| `updownio_request_total` | counter   | `alias`, `tag`   | requests seen by updown.io, per category (`samples`, `failures`, `satisfied`, `tolerated`) |
| `updownio_response_time` | histogram | `alias`          | response time in ms; buckets 125 to 32000 |
| `updownio_ssl_remaining` | gauge     | `alias`          | seconds until the certificate expires; only for checks with SSL |
| `daemon_config_info`     | info      | `name`           | running configuration and exporter version |
| `daemon`                 | gauge     | `name`, `status` | `items-ok` / `items-ko`: checks whose last poll succeeded or failed; `loop-reset`: count of window restarts |
| `daemon_time`            | histogram | `name`, `status` | duration of each polling loop, in seconds |

The `daemon*` metrics come from
[daemon-metrics](https://pypi.org/project/daemon-metrics/) and are shared with
the other exporters built on it.

### How counters are built

updown.io does not expose counters. It returns totals over a time window
starting at a given date. The exporter keeps that start date fixed, and on each
poll adds the difference with the previous totals to the Prometheus counters.

The histogram is rebuilt the same way from updown.io's `under<N>` buckets.
updown.io only gives counts per bucket, so each new sample is recorded at the
midpoint of its bucket. Samples slower than 32 s are recorded at 64 s.

Two things reset this state:

- **Every `loop.reset` hours**, the window restarts at `now - loop.lookback`.
  The cache and every `updownio_*` series are cleared, so checks deleted or
  disabled since then disappear, and `daemon{status="loop-reset"}` is
  incremented.
- **When a total decreases** between two polls, that check's affected series is
  dropped and starts over on the next poll.

Both show up to Prometheus as ordinary counter resets, so `rate()` and
`increase()` handle them.

## Development

```shell
poetry install
make help    # list the targets
make lint    # pycodestyle, mypy, pylint, black --check, poetry check
make build   # wheel + local docker image tagged with the current branch
```

## Releasing

Releases are built and published from the local machine; there is no CI.

```shell
make release   # bump the version, commit "release X.Y.Z", tag X.Y.Z, push
make deploy    # PyPI, then Docker Hub as vX, vX.Y, vX.Y.Z and latest
```

Images are pushed to `$(REGISTRY)`, `jaesivsm` (Docker Hub) by default, for
`linux/amd64` and `linux/arm64` (`PLATFORMS`). `make push-tag TAG=<tag>` pushes
a one-off tag. Releases up to 0.0.12 were tagged `0.0.N` without the `v`.

