aboutsummaryrefslogtreecommitdiff
path: root/prometheus_borgmatic_exporter/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'prometheus_borgmatic_exporter/__init__.py')
-rw-r--r--prometheus_borgmatic_exporter/__init__.py134
1 files changed, 59 insertions, 75 deletions
diff --git a/prometheus_borgmatic_exporter/__init__.py b/prometheus_borgmatic_exporter/__init__.py
index 2cfcce5..8bfd629 100644
--- a/prometheus_borgmatic_exporter/__init__.py
+++ b/prometheus_borgmatic_exporter/__init__.py
@@ -89,14 +89,11 @@ def run_command(
Logs the command at debug level before execution. Exits the application
with the command's return code if it fails.
- Args:
- ctx: The Click context, injected by @click.pass_context.
- command: The command and its arguments as a list of strings.
-
- Returns:
- The completed process result with stdout and stderr captured.
+ :param ctx: Click context injected by :func:`click.pass_context`.
+ :param command: Command and its arguments.
+ :return: Completed process with stdout and stderr captured.
"""
- debug("Running command", " ".join(command))
+ debug("Running command", " ".join(command), err=True)
try:
return subprocess.run(
command, capture_output=True, encoding="utf-8", check=True
@@ -112,15 +109,12 @@ def run_command(
def load_json(ctx: click.Context, raw: str) -> Any:
"""Parse a JSON string and return the result.
- Exits the application with code 1 if the input is not valid JSON, printing
- the parse error via the error() helper.
-
- Args:
- ctx: The Click context, injected by @click.pass_context.
- raw: The raw JSON string to parse.
+ Exits the application with status code 1 if the input is not valid JSON and
+ reports the parsing error using :func:`error`.
- Returns:
- The parsed JSON value.
+ :param ctx: Click context injected by :func:`click.pass_context`.
+ :param raw: Raw JSON string to parse.
+ :return: Parsed JSON value.
"""
try:
return json.loads(raw)
@@ -132,15 +126,11 @@ def load_json(ctx: click.Context, raw: str) -> Any:
def extract_archive_counts(
repositories: list[types.BorgInfoReturnType] | list[types.BorgListReturnType],
) -> list[types.ArchiveCount]:
- """Extract archive counts and repository metadata from borgmatic info output.
+ """Extract archive counts and repository metadata from borgmatic output.
- Args:
- repositories: List of repository objects as returned by borgmatic info
- or borgmatic list.
-
- Returns:
- A list of ArchiveCount dicts, one per repository, containing the
- repository id, label, location, and total number of archives.
+ :param repositories: Repository objects returned by ``borgmatic info`` or
+ ``borgmatic list``.
+ :return: Archive counts and identifying metadata for each repository.
"""
return [
{
@@ -158,29 +148,24 @@ def fetch_repositories(
) -> tuple[list[types.BorgInfoReturnType], list[types.ArchiveCount]]:
"""Fetch repository and archive data from borgmatic.
- When all_archives is False, runs `borgmatic info --archive latest` and
- `borgmatic list` in complementary intent: info provides per-repository
- stats and the latest archive metrics, while list provides the total archive
- count — since info with --archive latest only returns a single archive,
- making len() unreliable for counting.
-
- When all_archives is True, only `borgmatic info` is run. The full archive
- list is available directly from the info output, so no separate list call
- is needed and archive counts are derived from it.
+ When ``all_archives`` is false, runs ``borgmatic info --archive latest``
+ and ``borgmatic list``. The former provides per-repository statistics and
+ metrics for the latest archive, while the latter provides the complete
+ archive list needed to determine archive counts.
- Args:
- borgmatic_bin: Path to the borgmatic executable.
- borgmatic_config: Path to the borgmatic configuration file.
- all_archives: If True, fetch and return metrics for all archives.
- This is significantly slower for large repositories.
+ When ``all_archives`` is true, only ``borgmatic info`` is run. Its output
+ already contains the complete archive list, so archive counts can be
+ derived directly from it.
- Returns:
- A tuple of:
- - repositories: Raw borgmatic info output, one entry per repository.
- - archive_counts: Archive count and identifying metadata per repository,
- always populated regardless of the all_archives flag.
+ :param borgmatic_bin: Path to the borgmatic executable.
+ :param borgmatic_configs: Paths to borgmatic configuration files or
+ directories.
+ :param all_archives: Whether to fetch metrics for all archives instead of
+ only the latest archive.
+ :return: Raw ``borgmatic info`` output and archive counts with identifying
+ metadata for each repository.
"""
- verbose("Fetching repositories")
+ verbose("Fetching repositories", err=True)
borgmatic_cmd = [str(borgmatic_bin)]
for config in borgmatic_configs:
@@ -193,7 +178,7 @@ def fetch_repositories(
repositories = cast(
list[types.BorgInfoReturnType], load_json(borgmatic_info.stdout)
)
- debug(f"Fetched {len(repositories)} repositories")
+ debug(f"Fetched {len(repositories)} repositories", err=True)
return repositories, extract_archive_counts(repositories)
info_command += ["--archive", "latest"]
@@ -201,7 +186,7 @@ def fetch_repositories(
repositories = cast(
list[types.BorgInfoReturnType], load_json(borgmatic_info.stdout)
)
- debug(f"Fetched {len(repositories)} repositories")
+ debug(f"Fetched {len(repositories)} repositories", err=True)
borgmatic_list = run_command([*borgmatic_cmd, "list", "--json"])
archives = cast(list[types.BorgListReturnType], load_json(borgmatic_list.stdout))
@@ -211,16 +196,14 @@ def fetch_repositories(
def collect_repository_metrics(
repository: types.BorgInfoReturnType, all_archives: bool
) -> None:
- """Collect and set all Prometheus metrics for a single repository.
+ """Collect Prometheus metrics for a repository.
- Sets cache stats, encryption info, last modified timestamp, and latest
- archive metrics. When all_archives is True, also collects metrics for every
- individual archive by calling _collect_archive_metrics.
+ Sets cache statistics, encryption information, the last-modified timestamp,
+ and metrics for the latest archive. When ``all_archives`` is true, also
+ collects metrics for every individual archive.
- Args:
- repository: Repository info as returned by borgmatic.
- all_archives: If True, collect metrics for all archives, not just
- the latest.
+ :param repository: Repository information returned by borgmatic.
+ :param all_archives: Whether to collect metrics for every archive.
"""
repository_metadata = repository["repository"]
@@ -229,6 +212,7 @@ def collect_repository_metrics(
repository_metadata["label"],
f"({repository_metadata['id']})",
repository_metadata["location"],
+ err=True,
)
repository_labels: types.RepositoryLabels = {
@@ -260,11 +244,15 @@ def collect_repository_metrics(
metric.labels(**repository_labels).set(repository_stats[key])
if not repository["archives"]:
- warn("No archives found for repository:", repository_metadata["label"])
+ warn(
+ "No archives found for repository:", repository_metadata["label"], err=True
+ )
return
latest_archive = max(repository["archives"], key=lambda archive: archive["start"])
- debug(f"Latest archive {latest_archive['name']}:", latest_archive["start"])
+ debug(
+ f"Latest archive {latest_archive['name']}:", latest_archive["start"], err=True
+ )
metrics.LATEST_ARCHIVE_DURATION.labels(**repository_labels).set(
latest_archive["duration"]
@@ -287,14 +275,12 @@ def collect_repository_metrics(
def collect_archive_metrics(
archive: types.BorgArchive, repository_labels: types.RepositoryLabels
) -> None:
- """Collect and set Prometheus metrics for a single archive.
+ """Collect Prometheus metrics for an archive.
- Args:
- archive: Archive info as returned by borgmatic.
- repository_labels: The parent repository's label dict, used to
- populate the shared label dimensions on each metric.
+ :param archive: Archive information returned by borgmatic.
+ :param repository_labels: Labels inherited from the parent repository.
"""
- msg("Processing archive:", archive["name"], f"({archive['id']})")
+ msg("Processing archive:", archive["name"], f"({archive['id']})", err=True)
archive_labels = {
**repository_labels,
@@ -406,15 +392,12 @@ def prometheus_borgmatic_exporter(
"""Collect borgmatic repository metrics and export them for Prometheus.
Queries borgmatic for repository and archive statistics and writes them to
- a .prom file for consumption by the Prometheus node exporter textfile
+ a ``.prom`` file for consumption by the Prometheus node exporter textfile
collector.
- By default only the latest archive is queried per repository. Use
- --all-archives to collect metrics for every archive, at the cost of a
- significantly longer runtime.
-
- Using --all-archives will create one time series per archive and may
- significantly increase Prometheus storage usage.
+ By default, only the latest archive is queried for each repository. Use
+ ``--all-archives`` to collect metrics for every archive, at the cost of a
+ significantly longer runtime and increased Prometheus storage usage.
"""
ctx.ensure_object(dict)
@@ -422,17 +405,18 @@ def prometheus_borgmatic_exporter(
ctx.obj["quiet"] = quiet_flag
ctx.obj["verbose"] = verbose_flag
- debug("Effective configuration:")
- debug(" borgmatic_bin:", str(borgmatic_bin))
+ debug("Effective configuration:", err=True)
+ debug(" borgmatic_bin:", str(borgmatic_bin), err=True)
debug(
" borgmatic_configs:",
", ".join(str(config) for config in borgmatic_configs)
or "<borgmatic defaults>",
+ err=True,
)
- debug(" textfile_collector_dir:", str(textfile_collector_dir))
- debug(" all_archives:", str(all_archives_flag))
- debug(" verbose:", str(verbose_flag))
- debug(" quiet:", str(quiet_flag))
+ debug(" textfile_collector_dir:", str(textfile_collector_dir), err=True)
+ debug(" all_archives:", str(all_archives_flag), err=True)
+ debug(" verbose:", str(verbose_flag), err=True)
+ debug(" quiet:", str(quiet_flag), err=True)
repositories, archive_counts = fetch_repositories(
borgmatic_bin, borgmatic_configs, all_archives_flag
@@ -450,7 +434,7 @@ def prometheus_borgmatic_exporter(
metrics_file = textfile_collector_dir / "borgmatic.prom"
write_to_textfile(str(metrics_file.absolute()), metrics.REGISTRY)
- msg("Metrics written to:", str(metrics_file.absolute()))
+ msg("Metrics written to:", str(metrics_file.absolute()), err=True)
if __name__ == "__main__":