feat(us-025): model usage statistics with rolling RPM windows and SQLite persistence
Adds _RollingCounter and _StatsCollector to the tracker: three rolling windows (hour=60×1min, day=24×1hr, month=30×1day) track request RPMs per model. GET /v1/stats returns combined local + peer stats. POST /v1/stats/gossip lets trackers push their local slice for additive merge in hive mode. SQLite persistence via --stats-db flag; stats survive tracker restarts. Records a stat for each proxied /v1/chat/completions request. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1089,3 +1089,99 @@ def test_two_node_pipeline_via_tracker():
|
||||
node_a.stop()
|
||||
node_b.stop()
|
||||
tracker.stop()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- US-025 stats
|
||||
|
||||
def test_stats_endpoint_returns_empty_models_initially():
|
||||
tracker = TrackerServer()
|
||||
port = tracker.start()
|
||||
try:
|
||||
resp = _get_json(f"http://127.0.0.1:{port}/v1/stats")
|
||||
assert "models" in resp
|
||||
assert isinstance(resp["models"], dict)
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
|
||||
def test_stats_rolling_counter_rpm_reflects_recorded_requests():
|
||||
from meshnet_tracker.server import _RollingCounter
|
||||
|
||||
counter = _RollingCounter(num_buckets=60, bucket_seconds=60)
|
||||
now = 1_000_000.0
|
||||
for _ in range(60):
|
||||
counter.record(now)
|
||||
|
||||
rpm = counter.rpm(now)
|
||||
assert rpm == 1.0 # 60 requests over 60 minutes = 1 rpm
|
||||
|
||||
|
||||
def test_stats_collector_records_and_returns_rpms():
|
||||
from meshnet_tracker.server import _StatsCollector
|
||||
|
||||
collector = _StatsCollector()
|
||||
now = 1_000_000.0
|
||||
for _ in range(30):
|
||||
collector.record_request("my-model", now=now)
|
||||
|
||||
combined = collector.get_combined_stats(now=now)
|
||||
assert "my-model" in combined
|
||||
assert combined["my-model"]["rpm_last_hour"] > 0
|
||||
assert combined["my-model"]["rpm_last_day"] > 0
|
||||
assert combined["my-model"]["rpm_last_month"] > 0
|
||||
|
||||
|
||||
def test_stats_collector_merges_peer_rpms_additively():
|
||||
from meshnet_tracker.server import _StatsCollector
|
||||
|
||||
collector = _StatsCollector()
|
||||
now = 1_000_000.0
|
||||
collector.record_request("modelX", now=now) # 1 local request in the current minute bucket
|
||||
|
||||
peer_rpms = {"modelX": {"rpm_last_hour": 5.0, "rpm_last_day": 3.0, "rpm_last_month": 1.0}}
|
||||
collector.merge_peer_rpms("http://peer-tracker:8080", peer_rpms)
|
||||
|
||||
combined = collector.get_combined_stats(now=now)
|
||||
assert combined["modelX"]["rpm_last_hour"] > 5.0 # local + peer
|
||||
assert combined["modelX"]["rpm_last_day"] > 3.0
|
||||
assert combined["modelX"]["rpm_last_month"] >= 1.0 # local rounds to 0 at 4dp, peer is 1.0
|
||||
|
||||
|
||||
def test_stats_sqlite_persistence_survives_restart(tmp_path):
|
||||
from meshnet_tracker.server import _StatsCollector
|
||||
|
||||
db = str(tmp_path / "stats.db")
|
||||
now = 1_000_000.0
|
||||
|
||||
collector = _StatsCollector(db_path=db)
|
||||
for _ in range(10):
|
||||
collector.record_request("persistent-model", now=now)
|
||||
collector.save_to_db()
|
||||
|
||||
collector2 = _StatsCollector(db_path=db)
|
||||
combined = collector2.get_combined_stats(now=now)
|
||||
assert "persistent-model" in combined
|
||||
assert combined["persistent-model"]["rpm_last_hour"] > 0
|
||||
|
||||
|
||||
def test_stats_gossip_endpoint_merges_peer_slice():
|
||||
tracker = TrackerServer()
|
||||
port = tracker.start()
|
||||
try:
|
||||
peer_payload = {
|
||||
"tracker_url": "http://peer:9000",
|
||||
"stats": {
|
||||
"remote-model": {
|
||||
"rpm_last_hour": 2.5,
|
||||
"rpm_last_day": 1.0,
|
||||
"rpm_last_month": 0.5,
|
||||
}
|
||||
},
|
||||
}
|
||||
_post_json(f"http://127.0.0.1:{port}/v1/stats/gossip", peer_payload)
|
||||
|
||||
combined = _get_json(f"http://127.0.0.1:{port}/v1/stats")
|
||||
assert "remote-model" in combined["models"]
|
||||
assert combined["models"]["remote-model"]["rpm_last_hour"] == 2.5
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
Reference in New Issue
Block a user