Bashperfmon/server/server.py
isaac@edgeandnode.com 206faa3b89 inital commit
2025-09-24 01:32:19 -07:00

108 lines
3.6 KiB
Python

# server.py
import os, json, time, sqlite3
from datetime import datetime
from typing import Any
from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
import re
AUTH_KEY = os.getenv("AUTH_KEY", "dred123")
ROLLING_WINDOW_SECONDS = 3600 * 6 # 6 hours
app = Flask(__name__)
CORS(app)
def normalize_sqlite_key(key: str) -> str:
# Replace anything not alphanum or underscore with underscore
return re.sub(r'[^a-zA-Z0-9_]', '_', key)
def get_db_path(hostname: str) -> str:
today = datetime.now().strftime("%Y-%m-%d")
host_clean = hostname.replace(" ", "_").replace("-", "_")
path = f"./dbs/{today}/{host_clean}.sqlite3"
os.makedirs(os.path.dirname(path), exist_ok=True)
return path
@app.route('/api/metrics', methods=['POST'])
def receive_metrics():
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if token != AUTH_KEY:
return jsonify({"error": "unauthorized"}), 403
data = request.get_json()
if not data:
return jsonify({"error": "invalid JSON"}), 400
host = data.get("hostname") # crude host ID
timestamp = int(data.get("time", time.time()))
db_path = get_db_path(host)
table_name = f"{host.replace('-', '_')}" # sanitize
with sqlite3.connect(db_path) as db:
cursor = db.cursor()
# Create or alter table based on incoming keys
raw_keys = list(data.keys())
safe_keys = [normalize_sqlite_key(k) for k in raw_keys]
key_map = dict(zip(raw_keys, safe_keys))
columns = ", ".join([f"\"{k}\" TEXT" for k in safe_keys])
cursor.execute(f"""
CREATE TABLE IF NOT EXISTS "{table_name}" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp INTEGER,
{columns}
)
""")
existing_cols = set(row[1] for row in cursor.execute(f"PRAGMA table_info('{table_name}')"))
for safe_k in safe_keys:
if safe_k not in existing_cols:
cursor.execute(f'ALTER TABLE "{table_name}" ADD COLUMN "{safe_k}" TEXT')
# Insert data
placeholders = ", ".join(["?"] * (1 + len(safe_keys))) # 1 for timestamp
cols = ", ".join(["timestamp"] + list(safe_keys))
values = [timestamp] + [
json.dumps(data[k]) if isinstance(data[k], (dict, list)) else str(data[k]) for k in raw_keys
]
cursor.execute(f'INSERT INTO "{table_name}" ({cols}) VALUES ({placeholders})', values)
db.commit()
return jsonify({"status": "ok", "table": table_name}), 200
@app.route('/api/data/<host>')
def serve_host_data(host) -> Any:
db_path = get_db_path(host)
cutoff = int(time.time()) - ROLLING_WINDOW_SECONDS
table_name = f"{host.replace('-', '_')}"
with sqlite3.connect(db_path) as db:
cursor = db.cursor()
try:
cur = cursor.execute(
f"SELECT timestamp, * FROM '{table_name}' WHERE timestamp > ? ORDER BY timestamp ASC",
(cutoff,)
)
cols = [desc[0] for desc in cur.description]
entries = [dict(zip(cols, row)) for row in cur.fetchall()]
except sqlite3.OperationalError:
return jsonify([]) # Empty if table doesn't exist yet
return jsonify(entries)
@app.route('/db/<host>')
def serve_sqlite_copy(host):
db_path = get_db_path(host)
if not os.path.exists(db_path):
return jsonify({"error": "No DB yet for this host"}), 404
if os.path.getsize(db_path) < 3_000_000: # ~3MB max
return send_file(db_path, mimetype='application/octet-stream')
return jsonify({"error": "DB too large"}), 413
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7331)