Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pre-commit hooks and prometheus stats #261

Merged
merged 3 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.9.4
hooks:
# Run the linter.
- id: ruff
args: [ --fix ]
# Run the formatter.
- id: ruff-format
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ formats:
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requires.txt
- requirements: docs/requires.txt
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"python.formatting.provider": "black"
}
}
10 changes: 8 additions & 2 deletions abrechnung/application/groups.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from datetime import datetime
from datetime import datetime, timedelta

import asyncpg
from sftkit.database import Connection
from sftkit.error import InvalidArgument
from sftkit.service import Service, with_db_transaction
from sftkit.service import Service, with_db_connection, with_db_transaction

from abrechnung.config import Config
from abrechnung.core.auth import create_group_log
Expand All @@ -20,6 +20,7 @@
GroupPreview,
)
from abrechnung.domain.users import User
from abrechnung.util import timed_cache


class GroupService(Service[Config]):
Expand Down Expand Up @@ -461,3 +462,8 @@ async def unarchive_group(self, *, conn: Connection, user: User, group_id: int):
"update grp set archived = false where id = $1",
group_id,
)

@with_db_connection
@timed_cache(timedelta(minutes=5))
async def total_number_of_groups(self, conn: Connection):
return await conn.fetchval("select count(*) from grp")
22 changes: 20 additions & 2 deletions abrechnung/application/transactions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import base64
from datetime import datetime
from datetime import datetime, timedelta
from typing import Optional, Union

import asyncpg
from sftkit.database import Connection
from sftkit.error import InvalidArgument
from sftkit.service import Service, with_db_transaction
from sftkit.service import Service, with_db_connection, with_db_transaction

from abrechnung.application.common import _get_or_create_tag_ids
from abrechnung.config import Config
Expand All @@ -25,6 +25,7 @@
UpdateTransaction,
)
from abrechnung.domain.users import User
from abrechnung.util import timed_cache


class TransactionService(Service[Config]):
Expand Down Expand Up @@ -721,3 +722,20 @@ async def _create_pending_transaction_change(
)

return revision_id

@with_db_connection
@timed_cache(timedelta(minutes=5))
async def total_number_of_transactions(self, conn: Connection):
return await conn.fetchval("select count(*) from transaction_state_valid_at(now()) where not deleted")

@with_db_connection
@timed_cache(timedelta(minutes=5))
async def total_amount_of_money_per_currency(self, conn: Connection) -> dict[str, float]:
result = await conn.fetch(
"select t.currency_symbol, sum(t.value) as total_value "
"from transaction_state_valid_at(now()) as t where not t.deleted group by t.currency_symbol"
)
aggregated = {}
for row in result:
aggregated[row["currency_symbol"]] = row["total_value"]
return aggregated
6 changes: 6 additions & 0 deletions abrechnung/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ class AuthConfig(BaseModel):
auth: Optional[AuthConfig] = None


class MetricsConfig(BaseModel):
enabled: bool = False
expose_money_amounts: bool = False


class Config(BaseSettings):
model_config = SettingsConfigDict(env_prefix="ABRECHNUNG_", env_nested_delimiter="__")

Expand All @@ -77,6 +82,7 @@ class Config(BaseSettings):
# in case all params are optional this is needed to make the whole section optional
demo: DemoConfig = DemoConfig()
registration: RegistrationConfig = RegistrationConfig()
metrics: MetricsConfig = MetricsConfig()

@classmethod
def settings_customise_sources(
Expand Down
2 changes: 1 addition & 1 deletion abrechnung/database/revisions/0003_purchase_items.sql
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ create table if not exists purchase_item_usage (

-- amount of shares this account has from the purchase item
share_amount double precision not null
);
);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- migration: dbcccb58
-- requires: a77c9b57

alter table group_invite add column join_as_editor bool default false;
alter table group_invite add column join_as_editor bool default false;
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
-- requires: 156aef63

alter table transaction_revision add column version int default 0;
alter table account_revision add column version int default 0;
alter table account_revision add column version int default 0;
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@
-- requires: 5b333d87

alter table usr add column is_guest_user bool default false not null;

Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,3 @@ create table account_to_tag (
tag_id integer not null references tag (id),
primary key (account_id, revision_id, tag_id)
);

Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
alter table transaction_history alter column description set default '';
alter table transaction_history alter column description set not null;

alter table grp alter column created_by set not null;
alter table grp alter column created_by set not null;
13 changes: 13 additions & 0 deletions abrechnung/http/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging

from prometheus_fastapi_instrumentator import Instrumentator
from sftkit.http import Server

from abrechnung import __version__
Expand All @@ -11,6 +12,7 @@
from abrechnung.config import Config
from abrechnung.database.migrations import check_revision_version, get_database

from . import metrics
from .context import Context
from .routers import accounts, auth, common, groups, transactions

Expand Down Expand Up @@ -64,9 +66,20 @@ async def _setup(self):
async def _teardown(self):
await self.db_pool.close()

def _instrument_api(self):
if not self.cfg.metrics.enabled:
return
instrumentor = Instrumentator()
instrumentor.add(metrics.abrechnung_number_of_groups_total(self.group_service))
instrumentor.add(metrics.abrechnung_number_of_transactions_total(self.transaction_service))
if self.cfg.metrics.expose_money_amounts:
instrumentor.add(metrics.abrechnung_total_amount_of_money(self.transaction_service))
instrumentor.instrument(self.server.api).expose(self.server.api, endpoint="/api/metrics")

async def run(self):
await self._setup()
try:
self._instrument_api()
await self.server.run(self.context)
finally:
await self._teardown()
46 changes: 46 additions & 0 deletions abrechnung/http/metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from prometheus_client import Gauge
from prometheus_fastapi_instrumentator.metrics import Info

from abrechnung.application.groups import GroupService
from abrechnung.application.transactions import TransactionService


def abrechnung_number_of_groups_total(group_service: GroupService):
metric = Gauge(
"abrechnung_number_of_groups_total",
"Number of groups created on this Abrechnung instance..",
)

async def instrumentation(info: Info) -> None:
total = await group_service.total_number_of_groups()
metric.set(total)

return instrumentation


def abrechnung_number_of_transactions_total(transaction_service: TransactionService):
metric = Gauge(
"abrechnung_number_of_transactions_total",
"Number of transactions created on this Abrechnung instance..",
)

async def instrumentation(info: Info) -> None:
total = await transaction_service.total_number_of_transactions()
metric.set(total)

return instrumentation


def abrechnung_total_amount_of_money(transaction_service: TransactionService):
metric = Gauge(
"abrechnung_total_amount_of_money",
"Total amount of money per currency cleared via thisthis Abrechnung instance..",
labelnames=("currency_symbol",),
)

async def instrumentation(info: Info) -> None:
total = await transaction_service.total_amount_of_money_per_currency()
for currency, value in total.items():
metric.labels(currency).set(value)

return instrumentation
21 changes: 21 additions & 0 deletions abrechnung/util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import functools
import logging
import re
import uuid
Expand Down Expand Up @@ -72,3 +73,23 @@ def is_valid_uuid(val: str):
return True
except ValueError:
return False


def timed_cache(ttl: timedelta):
def wrapper(func):
last_execution: datetime | None = None
last_value = None

@functools.wraps(func)
async def wrapped(*args, **kwargs):
nonlocal last_execution, last_value
current_execution = datetime.now()
if last_execution is None or last_value is None or current_execution - last_execution > ttl:
last_value = await func(*args, **kwargs)
last_execution = current_execution

return last_value

return wrapped

return wrapper
2 changes: 1 addition & 1 deletion config/nginx/abrechnung
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ server {
location / {
try_files $uri /index.html =404;
}
}
}
2 changes: 1 addition & 1 deletion debian/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ python3-abrechnung
debhelper-build-stamp
files
*.substvars
tmp
tmp
2 changes: 1 addition & 1 deletion debian/abrechnung-api.service
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ User=abrechnung
Group=abrechnung

[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target
11 changes: 0 additions & 11 deletions debian/abrechnung-db-clean.service

This file was deleted.

8 changes: 0 additions & 8 deletions debian/abrechnung-db-clean.timer

This file was deleted.

2 changes: 1 addition & 1 deletion debian/abrechnung.dirs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
etc/abrechnung
etc/abrechnung
2 changes: 1 addition & 1 deletion debian/abrechnung.links
Original file line number Diff line number Diff line change
@@ -1 +1 @@
opt/venvs/abrechnung/bin/abrechnung usr/bin/abrechnung
opt/venvs/abrechnung/bin/abrechnung usr/bin/abrechnung
4 changes: 2 additions & 2 deletions debian/changelog
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ abrechnung (1.0.0) stable; urgency=medium

* Abrechnung release 1.0.0

-- Michael Loipführer <[email protected]> Fri, 3 Jan 2025 16:56:40
-- Michael Loipführer <[email protected]> Fri, 3 Jan 2025 16:56:40

abrechnung (0.14.0) stable; urgency=medium

* Abrechnung release 0.14.0

-- Michael Loipführer <[email protected]> Fri, 16 Aug 2024 23:57:11
-- Michael Loipführer <[email protected]> Fri, 16 Aug 2024 23:57:11

abrechnung (0.13.3) stable; urgency=medium

Expand Down
2 changes: 1 addition & 1 deletion debian/compat
Original file line number Diff line number Diff line change
@@ -1 +1 @@
11
11
2 changes: 1 addition & 1 deletion docker/Dockerfile-api
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ ADD --chmod=644 --chown=abrechnung:abrechnung docker/abrechnung.yaml /etc/abrech
ADD --chmod=755 ./docker/entrypoint.py /
COPY --chown=abrechnung:abrechnung ./docker/crontab /var/spool/cron/crontabs/abrechnung
USER abrechnung
ENTRYPOINT ["/opt/abrechnung-venv/bin/python3", "-u", "/entrypoint.py"]
ENTRYPOINT ["/opt/abrechnung-venv/bin/python3", "-u", "/entrypoint.py"]
2 changes: 1 addition & 1 deletion docker/Dockerfile-devel
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1.3
FROM node:lts as build
ADD frontend/ /build/
RUN cd /build/ && npm install && npx nx build web
RUN cd /build/ && npm install && npx nx build web

FROM node:lts as frontend-dev
COPY --chown=node:node frontend/ /frontend/
Expand Down
2 changes: 1 addition & 1 deletion docker/build_debian.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ for i in ../*.deb ../*.dsc ../*.tar.xz ../*.changes ../*.buildinfo; do
[ -z "$TARGET_USERID" ] || chown "$TARGET_USERID" "$i"
[ -z "$TARGET_GROUPID" ] || chgrp "$TARGET_GROUPID" "$i"
mv "$i" /debs
done
done
2 changes: 1 addition & 1 deletion docs/development/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ As Developers

Get started by setting up a local :ref:`development environment <abrechnung-dev-setup>` and reading through the
:ref:`abrechnung-dev-architecture` overview and then start hacking.
`Pull requests <https://github.com/SFTtech/abrechnung/pulls>`_ are always welcome!
`Pull requests <https://github.com/SFTtech/abrechnung/pulls>`_ are always welcome!
Loading