diff --git a/httpd.py b/httpd.py index bd48bbf..18d90ba 100644 --- a/httpd.py +++ b/httpd.py @@ -1,5 +1,6 @@ #!/usr/bin/env python2 # -*- coding: utf-8 -*- +from __future__ import division """HTTP API server with advanced web dashboard for PPF.""" import BaseHTTPServer @@ -7,9 +8,16 @@ import json import threading import time import os +import gc import mysqlite from misc import _log +# Memory tracking for leak detection +_memory_samples = [] +_memory_sample_max = 60 # Keep last 60 samples (5 min at 5s intervals) +_peak_rss = 0 +_start_rss = 0 + def get_system_stats(): """Collect system resource statistics.""" @@ -74,6 +82,33 @@ def get_system_stats(): stats['proc_rss'] = 0 stats['proc_threads'] = 0 + # Memory leak detection + global _memory_samples, _peak_rss, _start_rss + rss = stats.get('proc_rss', 0) + if rss > 0: + if _start_rss == 0: + _start_rss = rss + if rss > _peak_rss: + _peak_rss = rss + _memory_samples.append((time.time(), rss)) + if len(_memory_samples) > _memory_sample_max: + _memory_samples.pop(0) + + stats['proc_rss_peak'] = _peak_rss + stats['proc_rss_start'] = _start_rss + stats['proc_rss_growth'] = rss - _start_rss if _start_rss > 0 else 0 + + # GC stats for leak detection + try: + gc_counts = gc.get_count() + stats['gc_count_gen0'] = gc_counts[0] + stats['gc_count_gen1'] = gc_counts[1] + stats['gc_count_gen2'] = gc_counts[2] + stats['gc_objects'] = len(gc.get_objects()) + except Exception: + stats['gc_count_gen0'] = stats['gc_count_gen1'] = stats['gc_count_gen2'] = 0 + stats['gc_objects'] = 0 + return stats @@ -197,6 +232,7 @@ body { @keyframes pulse { 50% { opacity: 0.5; } } .mode-badge { padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; } .mode-ssl { background: rgba(63,185,80,0.2); color: var(--green); border: 1px solid var(--green); } +.mode-profile { background: rgba(255,165,0,0.2); color: #ffa500; border: 1px solid #ffa500; margin-left: 6px; } .mode-judges { background: rgba(88,166,255,0.2); color: var(--blue); border: 1px solid var(--blue); } .mode-http { background: rgba(210,153,34,0.2); color: var(--yellow); border: 1px solid var(--yellow); } .mode-irc { background: rgba(163,113,247,0.2); color: var(--purple); border: 1px solid var(--purple); } @@ -426,6 +462,11 @@ function update(d) { ctBadge.textContent = ct.toUpperCase(); ctBadge.className = 'mode-badge mode-' + ct; } + // Profiling badge + var profBadge = $('profileBadge'); + if (profBadge) { + profBadge.style.display = d.profiling ? 'inline-block' : 'none'; + } // System monitor bar var sys = d.system || {}; @@ -438,7 +479,9 @@ function update(d) { $('sysDiskPct').textContent = (sys.disk_pct || 0) + '%'; var diskFill = $('sysDiskFill'); if (diskFill) { diskFill.style.width = (sys.disk_pct || 0) + '%'; diskFill.style.cssText = 'width:' + (sys.disk_pct || 0) + '%;' + setBarColor(diskFill, sys.disk_pct || 0); } - $('sysProcMem').textContent = fmtBytes(sys.proc_rss); + var memGrowth = sys.proc_rss_growth || 0; + var memGrowthStr = memGrowth > 0 ? ' (+' + fmtBytes(memGrowth) + ')' : ''; + $('sysProcMem').textContent = fmtBytes(sys.proc_rss) + memGrowthStr; // Main stats - db stats are nested under d.db var db = d.db || {}; @@ -535,9 +578,12 @@ function update(d) { var thtml = ''; if (d.tor_pool && d.tor_pool.hosts) { d.tor_pool.hosts.forEach(function(h) { + // Status: OK only if available AND has successes, WARN if available but 0%, DOWN if in backoff + var statusCls = !h.healthy ? 'tag-err' : (h.success_rate > 0 ? 'tag-ok' : 'tag-warn'); + var statusTxt = !h.healthy ? 'DOWN' : (h.success_rate > 0 ? 'OK' : 'IDLE'); thtml += '