4 Commits
Author SHA1 Message Date
Iñigo Etxaniz 3c01596c38 add restart signals to other machines 2026-11-09 19:19:04 +00:00
Iñigo Etxaniz eff8ea4dfe bug 2026-11-07 08:34:53 +00:00
Iñigo Etxaniz 4de1d3e008 upgrade script 2026-11-07 08:30:59 +00:00
Iñigo Etxaniz 5220bd54c5 rename 2026-11-07 08:17:56 +00:00
4 changed files with 99 additions and 29 deletions
+4 -4
View File
@@ -1,7 +1,7 @@
# host-session
# host_session
Stores one row per finished session of the host in the custom table `host_session` (schema 0.0.1): start and end time, duration, mean and max of CPU, RAM and disk percentage, and coverage (share of the session's minutes with a CPU sample). The hub adds the gateway as `spoke_id`.
One row per session of the host in the custom table `host_session` (schema 0.0.1). The hub adds the gateway as `spoke_id`.
Runs every minute. The current boot comes from `/proc/uptime`. The previous session is found in the history of the uptime signal (63) before that boot: its last sample is the end, end minus that sample's uptime is the start. A session already stored (start within two minutes) is skipped, so repeated runs write nothing twice. The first row appears after the first reboot that follows the uptime signal's creation (2026-11-06 18:38).
The first run after a boot opens a row for the current session (start time equal to end time, everything else 0) and closes the previous one from the history of the host's uptime signal: CPU, RAM and disk at one minute, with coverage as the share of minutes with a CPU sample. Later runs in the same boot stop after one query.
Signal ids are deb13-kde's in the home core. Needs only `requests` in the image.
One script for every gateway. `signals.yml` lists the cpu, ram, disk and uptime signals of every host; the script asks the history of each `<host>-uptime` until one answers 200 (a gateway answers 403 for signals it does not own) and uses that host's four signals. A new host needs its four signals added to `signals.yml` and a new tag. Runs every minute; needs only `requests` in the image.
+65 -19
View File
@@ -1,4 +1,5 @@
import os
import re
import time
import requests
@@ -6,7 +7,7 @@ import requests
SIDECAR_URL = os.getenv("SIDECAR_URL", "http://sidecar:80")
DB_URL = os.getenv("DB_CONTROLLER_URL", "http://sidecar:8081")
TABLE = "host_session"
UPTIME, CPU, RAM, DISK = 63, 21, 22, 23
SIGNALS_FILE = "/app/signals.yml"
MINUTE_MS = 60000
HOUR_MS = 60 * MINUTE_MS
LOOKBACK_MS = 60 * 24 * HOUR_MS
@@ -18,7 +19,29 @@ def read_history(signals, start_ms, end_ms, resolution):
path = f"/api/history/?signals={names}&start={int(start_ms)}&end={int(end_ms)}&resolution={resolution}"
response = requests.get(SIDECAR_URL + path, timeout=(5, 80))
response.raise_for_status()
return {item["signalId"]: item for item in response.json()["data"]["series"]}
return {item["name"]: item for item in response.json()["data"]["series"]}
def signal_hosts():
with open(SIGNALS_FILE) as file:
names = re.findall(r"name:\s*'([^']+)'", file.read())
return sorted(name[: -len("-uptime")] for name in names if name.endswith("-uptime"))
def owns(host, now_ms):
path = f"/api/history/?signals={host}-uptime&start={int(now_ms - MINUTE_MS)}&end={int(now_ms)}&resolution=1min"
status = requests.get(SIDECAR_URL + path, timeout=(5, 80)).status_code
if status not in (200, 403):
raise RuntimeError(f"history probe for {host} answered {status}")
return status == 200
def own_host():
now_ms = time.time() * 1000
host = next((name for name in signal_hosts() if owns(name, now_ms)), None)
if host is None:
raise RuntimeError("no uptime signal of this gateway is listed in signals.yml")
return host
def samples(series):
@@ -39,14 +62,15 @@ def current_boot_ms():
return int(time.time() * 1000 - uptime_s * 1000)
def previous_session(boot_ms):
hours = last_samples(UPTIME, boot_ms - LOOKBACK_MS, boot_ms, "1hr")
def previous_session(host, boot_ms):
uptime = f"{host}-uptime"
hours = last_samples(uptime, boot_ms - LOOKBACK_MS, boot_ms, "1hr")
if not hours:
return None
minutes = last_samples(UPTIME, hours[-1][0], min(hours[-1][0] + HOUR_MS, boot_ms), "1min")
minutes = last_samples(uptime, hours[-1][0], min(hours[-1][0] + HOUR_MS, boot_ms), "1min")
if not minutes:
return None
seconds = last_samples(UPTIME, minutes[-1][0], min(minutes[-1][0] + MINUTE_MS, boot_ms), "1s")
seconds = last_samples(uptime, minutes[-1][0], min(minutes[-1][0] + MINUTE_MS, boot_ms), "1s")
end_ms, uptime_s = (seconds or minutes)[-1]
return int(end_ms - uptime_s * 1000), int(end_ms)
@@ -60,9 +84,19 @@ def db_call(method, path, payload):
return body.get("data")
def already_stored(start_ms):
rows = db_call("POST", f"/api/db-query/{TABLE}/", {"filters": [], "orderBy": ["start_time"], "limit": 1000}) or []
return any(abs(row["start_time"] - start_ms) < MATCH_MS for row in rows)
def stored_rows():
return db_call("POST", f"/api/db-query/{TABLE}/", {"filters": [], "orderBy": ["start_time"], "limit": 1000}) or []
def find_row(rows, start_ms):
return next((row for row in rows if abs(row["start_time"] - start_ms) < MATCH_MS), None)
def open_row(start_ms):
row = {"start_time": start_ms, "end_time": start_ms, "duration_s": 0, "coverage": 0}
for prefix in ("cpu", "ram", "disk"):
row[f"{prefix}_mean"], row[f"{prefix}_max"] = 0, 0
return row
def mean_max(series, start_ms, end_ms):
@@ -78,25 +112,37 @@ def coverage(series, start_ms, end_ms):
return min(1.0, present / total)
def build_row(start_ms, end_ms):
history = read_history([CPU, RAM, DISK], start_ms, end_ms, "1min")
def build_row(host, start_ms, end_ms):
cpu, ram, disk = f"{host}-cpu", f"{host}-ram", f"{host}-disk"
history = read_history([cpu, ram, disk], start_ms, end_ms, "1min")
row = {"start_time": start_ms, "end_time": end_ms, "duration_s": (end_ms - start_ms) // 1000}
for prefix, signal in (("cpu", CPU), ("ram", RAM), ("disk", DISK)):
for prefix, signal in (("cpu", cpu), ("ram", ram), ("disk", disk)):
row[f"{prefix}_mean"], row[f"{prefix}_max"] = mean_max(history[signal], start_ms, end_ms)
row["coverage"] = coverage(history[CPU], start_ms, end_ms)
row["coverage"] = coverage(history[cpu], start_ms, end_ms)
return row
def main():
session = previous_session(current_boot_ms())
def close_previous(host, rows, boot_ms):
session = previous_session(host, boot_ms)
if session is None:
print("no previous session in the uptime history", flush=True)
return
if already_stored(session[0]):
print("previous session already stored", session[0], flush=True)
row = build_row(host, *session)
stored = find_row(rows, session[0])
if stored is None:
print("previous session stored", db_call("POST", f"/api/db/{TABLE}/", row), row, flush=True)
elif stored["end_time"] == stored["start_time"]:
db_call("PUT", f"/api/db/{TABLE}/{stored['id']}/", row)
print("previous session closed", stored["id"], row, flush=True)
def main():
boot_ms = current_boot_ms()
rows = stored_rows()
if find_row(rows, boot_ms) is not None:
return
row = build_row(*session)
print("stored", db_call("POST", f"/api/db/{TABLE}/", row), row, flush=True)
close_previous(own_host(), rows, boot_ms)
print("current session opened", db_call("POST", f"/api/db/{TABLE}/", open_row(boot_ms)), flush=True)
if __name__ == "__main__":
+1 -1
View File
@@ -1,4 +1,4 @@
name: host-session
name: host_session
active: true
schedule: "* * * * *"
action: run
+24
View File
@@ -2,3 +2,27 @@
- { id: 22, name: 'deb13kde-ram', read: 1, write: 0 }
- { id: 23, name: 'deb13kde-disk', read: 1, write: 0 }
- { id: 63, name: 'deb13kde-uptime', read: 1, write: 0 }
- { id: 1, name: 'rbx-01-cpu', read: 1, write: 0 }
- { id: 2, name: 'rbx-01-ram', read: 1, write: 0 }
- { id: 3, name: 'rbx-01-disk', read: 1, write: 0 }
- { id: 64, name: 'rbx-01-uptime', read: 1, write: 0 }
- { id: 6, name: 'fr-01-cpu', read: 1, write: 0 }
- { id: 7, name: 'fr-01-ram', read: 1, write: 0 }
- { id: 8, name: 'fr-01-disk', read: 1, write: 0 }
- { id: 65, name: 'fr-01-uptime', read: 1, write: 0 }
- { id: 11, name: 'sb-01-cpu', read: 1, write: 0 }
- { id: 12, name: 'sb-01-ram', read: 1, write: 0 }
- { id: 13, name: 'sb-01-disk', read: 1, write: 0 }
- { id: 66, name: 'sb-01-uptime', read: 1, write: 0 }
- { id: 16, name: 'grab-01-cpu', read: 1, write: 0 }
- { id: 17, name: 'grab-01-ram', read: 1, write: 0 }
- { id: 18, name: 'grab-01-disk', read: 1, write: 0 }
- { id: 67, name: 'grab-01-uptime', read: 1, write: 0 }
- { id: 26, name: 'pi102-cpu', read: 1, write: 0 }
- { id: 27, name: 'pi102-ram', read: 1, write: 0 }
- { id: 28, name: 'pi102-disk', read: 1, write: 0 }
- { id: 68, name: 'pi102-uptime', read: 1, write: 0 }
- { id: 32, name: 'pi103-cpu', read: 1, write: 0 }
- { id: 33, name: 'pi103-ram', read: 1, write: 0 }
- { id: 34, name: 'pi103-disk', read: 1, write: 0 }
- { id: 69, name: 'pi103-uptime', read: 1, write: 0 }