mirror of
https://github.com/jorijn/meshcore-stats.git
synced 2026-03-28 17:42:55 +01:00
Compare commits
3 Commits
fix/counte
...
fix/docker
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a8c5dfdd9 | ||
|
|
0fc6ddfe3b | ||
|
|
5efb81baf0 |
24
.github/workflows/docker-publish.yml
vendored
24
.github/workflows/docker-publish.yml
vendored
@@ -250,27 +250,41 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
tag_suffix: amd64
|
||||
- platform: linux/arm64
|
||||
tag_suffix: arm64
|
||||
- platform: linux/arm/v7
|
||||
tag_suffix: armv7
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Build image (PR)
|
||||
- name: Build image (PR - ${{ matrix.platform }})
|
||||
id: build-pr
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
platforms: ${{ matrix.platform }}
|
||||
load: true
|
||||
push: false
|
||||
tags: meshcore-stats:pr-${{ github.event.pull_request.number }}
|
||||
tags: meshcore-stats:pr-${{ github.event.pull_request.number }}-${{ matrix.tag_suffix }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Smoke test (PR)
|
||||
- name: Smoke test (PR - ${{ matrix.platform }})
|
||||
run: |
|
||||
docker run --rm meshcore-stats:pr-${{ github.event.pull_request.number }} \
|
||||
docker run --rm --platform ${{ matrix.platform }} \
|
||||
meshcore-stats:pr-${{ github.event.pull_request.number }}-${{ matrix.tag_suffix }} \
|
||||
python -c "from meshmon.db import init_db; from meshmon.env import get_config; print('Smoke test passed')"
|
||||
|
||||
@@ -58,16 +58,19 @@ RUN set -ex; \
|
||||
if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
UV_ARCH="x86_64"; \
|
||||
UV_SHA256="$UV_SHA256_AMD64"; \
|
||||
UV_LIBC="gnu"; \
|
||||
elif [ "$TARGETARCH" = "arm64" ]; then \
|
||||
UV_ARCH="aarch64"; \
|
||||
UV_SHA256="$UV_SHA256_ARM64"; \
|
||||
UV_LIBC="gnu"; \
|
||||
elif [ "$TARGETARCH" = "arm" ] && [ "$TARGETVARIANT" = "v7" ]; then \
|
||||
UV_ARCH="armv7"; \
|
||||
UV_SHA256="$UV_SHA256_ARMV7"; \
|
||||
UV_LIBC="gnueabihf"; \
|
||||
else \
|
||||
echo "Unsupported architecture: $TARGETARCH${TARGETVARIANT:+/$TARGETVARIANT}" && exit 1; \
|
||||
fi; \
|
||||
curl -fsSL "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}-unknown-linux-gnu.tar.gz" \
|
||||
curl -fsSL "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}-unknown-linux-${UV_LIBC}.tar.gz" \
|
||||
-o /tmp/uv.tar.gz \
|
||||
&& echo "${UV_SHA256} /tmp/uv.tar.gz" | sha256sum -c - \
|
||||
&& tar -xzf /tmp/uv.tar.gz -C /usr/local/bin --strip-components=1 --wildcards "*/uv" \
|
||||
|
||||
@@ -36,7 +36,6 @@ ThemeName = Literal["light", "dark"]
|
||||
BIN_30_MINUTES = 1800 # 30 minutes in seconds
|
||||
BIN_2_HOURS = 7200 # 2 hours in seconds
|
||||
BIN_1_DAY = 86400 # 1 day in seconds
|
||||
MIN_COUNTER_INTERVAL_RATIO = 0.9 # Allow small scheduling jitter
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -224,38 +223,25 @@ def load_timeseries_from_db(
|
||||
# For counter metrics, calculate rate of change
|
||||
if is_counter:
|
||||
rate_points: list[tuple[datetime, float]] = []
|
||||
cfg = get_config()
|
||||
min_interval = max(
|
||||
1.0,
|
||||
(cfg.companion_step if role == "companion" else cfg.repeater_step)
|
||||
* MIN_COUNTER_INTERVAL_RATIO,
|
||||
)
|
||||
|
||||
prev_ts, prev_val = raw_points[0]
|
||||
for curr_ts, curr_val in raw_points[1:]:
|
||||
for i in range(1, len(raw_points)):
|
||||
prev_ts, prev_val = raw_points[i - 1]
|
||||
curr_ts, curr_val = raw_points[i]
|
||||
|
||||
delta_val = curr_val - prev_val
|
||||
delta_secs = (curr_ts - prev_ts).total_seconds()
|
||||
|
||||
if delta_secs <= 0:
|
||||
continue
|
||||
if delta_secs < min_interval:
|
||||
log.debug(
|
||||
f"Skipping counter sample for {metric} at {curr_ts} "
|
||||
f"({delta_secs:.1f}s < {min_interval:.1f}s)"
|
||||
)
|
||||
continue
|
||||
|
||||
delta_val = curr_val - prev_val
|
||||
|
||||
# Skip negative deltas (device reboot)
|
||||
if delta_val < 0:
|
||||
log.debug(f"Counter reset detected for {metric} at {curr_ts}")
|
||||
prev_ts, prev_val = curr_ts, curr_val
|
||||
continue
|
||||
|
||||
# Calculate per-second rate, then apply scaling (typically x60 for per-minute)
|
||||
rate = (delta_val / delta_secs) * scale
|
||||
rate_points.append((curr_ts, rate))
|
||||
prev_ts, prev_val = curr_ts, curr_val
|
||||
|
||||
raw_points = rate_points
|
||||
else:
|
||||
|
||||
@@ -67,49 +67,10 @@ class TestCounterToRateConversion:
|
||||
assert ts.points[0].value == pytest.approx(expected_rate)
|
||||
assert ts.points[1].value == pytest.approx(expected_rate)
|
||||
|
||||
def test_counter_rate_short_interval_under_step_is_skipped(
|
||||
self,
|
||||
initialized_db,
|
||||
configured_env,
|
||||
monkeypatch,
|
||||
):
|
||||
"""Short sampling intervals are skipped to avoid rate spikes."""
|
||||
base_ts = 1704067200
|
||||
|
||||
monkeypatch.setenv("REPEATER_STEP", "900")
|
||||
import meshmon.env
|
||||
|
||||
meshmon.env._config = None
|
||||
|
||||
insert_metrics(base_ts, "repeater", {"nb_recv": 0.0}, initialized_db)
|
||||
insert_metrics(base_ts + 900, "repeater", {"nb_recv": 100.0}, initialized_db)
|
||||
insert_metrics(base_ts + 904, "repeater", {"nb_recv": 110.0}, initialized_db)
|
||||
insert_metrics(base_ts + 1800, "repeater", {"nb_recv": 200.0}, initialized_db)
|
||||
|
||||
ts = load_timeseries_from_db(
|
||||
role="repeater",
|
||||
metric="nb_recv",
|
||||
end_time=datetime.fromtimestamp(base_ts + 1800),
|
||||
lookback=timedelta(hours=2),
|
||||
period="day",
|
||||
)
|
||||
|
||||
expected_rate = (100.0 / 900.0) * 60.0
|
||||
assert len(ts.points) == 2
|
||||
assert ts.points[0].timestamp == datetime.fromtimestamp(base_ts + 900)
|
||||
assert ts.points[1].timestamp == datetime.fromtimestamp(base_ts + 1800)
|
||||
for point in ts.points:
|
||||
assert point.value == pytest.approx(expected_rate)
|
||||
|
||||
def test_applies_scale_factor(self, initialized_db, configured_env, monkeypatch):
|
||||
def test_applies_scale_factor(self, initialized_db, configured_env):
|
||||
"""Counter rate is scaled (typically x60 for per-minute)."""
|
||||
base_ts = 1704067200
|
||||
|
||||
monkeypatch.setenv("REPEATER_STEP", "60")
|
||||
import meshmon.env
|
||||
|
||||
meshmon.env._config = None
|
||||
|
||||
# Insert values 60 seconds apart for easy math
|
||||
insert_metrics(base_ts, "repeater", {"nb_recv": 0.0}, initialized_db)
|
||||
insert_metrics(base_ts + 60, "repeater", {"nb_recv": 60.0}, initialized_db)
|
||||
|
||||
Reference in New Issue
Block a user