"""Mark VIII reliability event journal. JSONL on /mmc, rotated.""" import json, os, threading MK8_DIR = '/mmc/mk8' EVENTS_PATH = os.path.join(MK8_DIR, 'events.log') MAX_BYTES = 5 * 1024 * 1024 KEEP = 4 _LOCK = threading.Lock() COUNTER_KINDS = ('boot', 'unexpected_boot', 'rollback', 'restart', 'guard_fix') _COUNTER_KEYS = {'boot': 'boots', 'unexpected_boot': 'unexpected_boots', 'rollback': 'rollbacks', 'restart': 'restarts', 'guard_fix': 'guard_fixes'} def _ensure_dir(): try: os.makedirs(MK8_DIR, exist_ok=True) except OSError: pass def log_event(kind, sev='info', msg='', meta=None): entry = {'ts': int(__import__('time').time()), 'kind': str(kind), 'sev': sev, 'msg': msg[:500]} if meta: entry['meta'] = meta line = json.dumps(entry) + '\n' with _LOCK: _ensure_dir() try: if os.path.exists(EVENTS_PATH) and \ os.path.getsize(EVENTS_PATH) > MAX_BYTES: for i in range(KEEP - 1, 0, -1): src = '%s.%d' % (EVENTS_PATH, i) dst = '%s.%d' % (EVENTS_PATH, i + 1) if os.path.exists(src): os.replace(src, dst) if os.path.exists(EVENTS_PATH): os.replace(EVENTS_PATH, EVENTS_PATH + '.1') with open(EVENTS_PATH, 'a') as f: f.write(line) except OSError: pass def read_events(limit=100): out = [] paths = [EVENTS_PATH + '.%d' % i for i in range(KEEP, 0, -1)] paths.append(EVENTS_PATH) for path in paths: try: with open(path) as f: out.extend(json.loads(l) for l in f if l.strip()) except (OSError, ValueError): continue out.sort(key=lambda r: r.get('ts', 0)) return out[-limit:][::-1] def counters(): counts = {v: 0 for v in _COUNTER_KEYS.values()} for row in read_events(limit=5000): k = row.get('kind') if k in _COUNTER_KEYS: counts[_COUNTER_KEYS[k]] += 1 return counts def mark_boot(unexpected=False): log_event('unexpected_boot' if unexpected else 'boot', sev='warn' if unexpected else 'info', msg='service started' + ('' if unexpected else ' cleanly'))