betterwithage commited on
Commit
7b34d5c
·
verified ·
1 Parent(s): 676dc30

chore(sync): mirror backend .py + Dockerfile to Space (hf-sync-backend)

Browse files

Automated backend sync from szl-holdings/a11oy main via hf-sync-backend.
Updated (differed from the Space): szl_energy_operator.py
Deleted (gone from the repo + Dockerfile COPY set): (none)

Keeps the Space-built backend (serve.py + the Dockerfile-COPY'd .py
modules) identical to GitHub main so the Space never rebuilds from a
stale backend, new endpoints don't 404 there, and orphaned modules
removed from the repo don't linger in the Space tree.

Files changed (1) hide show
  1. szl_energy_operator.py +49 -4
szl_energy_operator.py CHANGED
@@ -524,19 +524,64 @@ _EMBED_TEXTS = [
524
  # ---------------------------------------------------------------------------
525
  # Exporter sampling — reuse the EXISTING betterwithage joule-meter path.
526
  # ---------------------------------------------------------------------------
527
- def _fetch_joule_meter(timeout: float = 4.0) -> Optional[dict]:
528
- """Fetch the live NVML joule-meter JSON, or None on any failure (honest)."""
 
 
 
 
 
 
 
 
 
 
 
 
529
  try:
530
  # Browser-like UA so a Cloudflare-fronted meter (e.g. meter.a-11-oy.com)
531
  # does not 403 the request behind bot protection. Honest self-probe.
532
- req = urllib.request.Request(
533
- _JOULE_METER_URL, headers={"User-Agent": _PROBE_UA})
534
  with urllib.request.urlopen(req, timeout=timeout) as r: # noqa: S310
535
  return json.loads(r.read().decode("utf-8", "replace"))
536
  except Exception: # noqa: BLE001 — unreachable meter => no sample, stay honest
537
  return None
538
 
539
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  def _exporter_sample_for_node(meter: Optional[dict], exporter_node: str,
541
  now: Optional[float] = None) -> Optional[dict]:
542
  """Build a szl_joules_truth exporter_sample for one node from the meter JSON.
 
524
  # ---------------------------------------------------------------------------
525
  # Exporter sampling — reuse the EXISTING betterwithage joule-meter path.
526
  # ---------------------------------------------------------------------------
527
+ def _joule_meter_urls() -> list[str]:
528
+ """All joule-meter URLs to scrape. A11OY_JOULE_METER_URLS (comma-separated) is the
529
+ multi-node form (e.g. omen's meter.a-11-oy.com + the laptop's meter2.a-11-oy.com);
530
+ falls back to the single A11OY_JOULE_METER_URL. De-duped, order preserved."""
531
+ multi = (os.environ.get("A11OY_JOULE_METER_URLS") or "").strip()
532
+ urls = [u.strip() for u in multi.split(",") if u.strip()] if multi else [_JOULE_METER_URL]
533
+ seen, out = set(), []
534
+ for u in urls:
535
+ if u and u not in seen:
536
+ seen.add(u); out.append(u)
537
+ return out
538
+
539
+
540
+ def _fetch_one_meter(url: str, timeout: float) -> Optional[dict]:
541
  try:
542
  # Browser-like UA so a Cloudflare-fronted meter (e.g. meter.a-11-oy.com)
543
  # does not 403 the request behind bot protection. Honest self-probe.
544
+ req = urllib.request.Request(url, headers={"User-Agent": _PROBE_UA})
 
545
  with urllib.request.urlopen(req, timeout=timeout) as r: # noqa: S310
546
  return json.loads(r.read().decode("utf-8", "replace"))
547
  except Exception: # noqa: BLE001 — unreachable meter => no sample, stay honest
548
  return None
549
 
550
 
551
+ def _fetch_joule_meter(timeout: float = 4.0) -> Optional[dict]:
552
+ """Fetch the live NVML joule-meter JSON, or None on any failure (honest).
553
+
554
+ Multi-node: scrapes every URL from _joule_meter_urls() and MERGES their engines[]
555
+ into one meter dict, so per-node lookups (_exporter_sample_for_node) see every GPU
556
+ in the mesh from a single call. Honest by design (Doctrine v11): an unreachable
557
+ meter contributes nothing (never faked); a duplicate engine name keeps the first
558
+ seen (no double-count). Single-URL config → original behaviour, unchanged.
559
+ """
560
+ urls = _joule_meter_urls()
561
+ if len(urls) == 1:
562
+ return _fetch_one_meter(urls[0], timeout)
563
+ merged_engines: list[dict] = []
564
+ total = 0.0
565
+ seen_names: set = set()
566
+ any_ok = False
567
+ for u in urls:
568
+ d = _fetch_one_meter(u, timeout)
569
+ if not isinstance(d, dict):
570
+ continue
571
+ any_ok = True
572
+ for e in (d.get("engines") or []):
573
+ name = str(e.get("engine") or "").lower()
574
+ if not name or name in seen_names:
575
+ continue
576
+ seen_names.add(name)
577
+ merged_engines.append(e)
578
+ if isinstance(e.get("joules"), (int, float)):
579
+ total += float(e["joules"])
580
+ if not any_ok:
581
+ return None
582
+ return {"engines": merged_engines, "totals": {"joules": round(total, 3)}}
583
+
584
+
585
  def _exporter_sample_for_node(meter: Optional[dict], exporter_node: str,
586
  now: Optional[float] = None) -> Optional[dict]:
587
  """Build a szl_joules_truth exporter_sample for one node from the meter JSON.