similarity / main.py
lpedro's picture
main - Approximate real RAM usage and shrink the oversized alerts panel
f20adc7
Raw History Blame Contribute Delete
7.15 kB
from contextlib import asynccontextmanager
from typing import List
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from models import RequestModel, ResponseModel
from similarity import (
check_similarity, get_dashboard_data, get_machine_stats,
start_memory_stats_reporter, start_memory_trimmer,
)
_CACHE_MAX = 500
@asynccontextmanager
async def lifespan(app: FastAPI):
start_memory_stats_reporter()
start_memory_trimmer(interval_seconds=900)
yield
app = FastAPI(title="Prozis Similarity Service", version="1.0.0", lifespan=lifespan)
_PAGE = ("""<!DOCTYPE html><html><head>
<meta charset="UTF-8">
<title>Prozis Similarity Service</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@1,800&family=Inter:wght@400;500&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%}
body{background:#0d0d14;color:#e2e8f0;font-family:'Inter',system-ui,sans-serif;padding:.875rem 1.5rem;
display:flex;flex-direction:column}
header{display:flex;align-items:baseline;gap:1rem;margin-bottom:.75rem;
padding-bottom:.5rem;border-bottom:1px solid #1a1a2e;flex-shrink:0}
h1{font-family:'Barlow Condensed',sans-serif;font-weight:800;font-style:italic;
font-size:1.85rem;color:#fff;letter-spacing:-.01em;line-height:1}
.meta{color:#64748b;font-size:.72rem;font-family:'SF Mono','Fira Code',monospace}
.meta b{color:#94a3b8}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:.5rem;flex-shrink:0}
.card{background:#111827;border:1px solid #1e2942;border-top:2px solid var(--c);
border-radius:8px;padding:.65rem .875rem}
.card-top{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:.35rem}
.pid{font-size:.62rem;color:#94a3b8;font-family:'SF Mono','Fira Code',monospace;
text-transform:uppercase;letter-spacing:.06em}
.cn{font-size:.6rem;color:#64748b;font-family:'SF Mono','Fira Code',monospace}
.rss{font-size:1.5rem;font-weight:700;font-family:'SF Mono','Fira Code',monospace;
color:var(--c);line-height:1;display:block}
.card-bot{display:flex;justify-content:space-between;align-items:baseline;margin-top:.15rem}
.unit{font-size:.6rem;color:#64748b;font-weight:400}
.req{font-size:.75rem;color:#94a3b8;font-family:'SF Mono','Fira Code',monospace}
.track{height:5px;background:#1e2942;border-radius:99px;margin-top:.45rem;overflow:hidden}
.fill{height:100%;border-radius:99px;opacity:.7}
.alerts-section{margin-top:.875rem;border-top:1px solid #1a1a2e;padding-top:.75rem;
flex-shrink:0}
.alerts-label{font-size:.65rem;color:#475569;font-family:'SF Mono','Fira Code',monospace;
text-transform:uppercase;letter-spacing:.08em;margin-bottom:.4rem;
display:flex;align-items:center;gap:.5rem}
.alerts-dot{width:6px;height:6px;border-radius:50%;background:#4ade80;box-shadow:0 0 6px #4ade80}
.alerts-dot.warn{background:#fab219;box-shadow:0 0 6px #fab219;animation:pulse 2s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
.alerts-box{background:#111827;border:1px solid #1e2942;border-radius:6px;
max-height:200px;overflow-y:auto;padding:.75rem .875rem;
font-family:'Inter',system-ui,sans-serif;font-size:.82rem;line-height:2}
.alert-line{display:flex;align-items:baseline;gap:.55rem;color:#f4f4f2}
.alert-empty{color:#475569;font-family:'SF Mono','Fira Code',monospace;font-size:.75rem}
</style>
</head><body>
<header>
<h1>Prozis Similarity Service</h1>
<span id="meta" class="meta"></span>
</header>
<div id="grid" class="grid"></div>
<div class="alerts-section">
<div class="alerts-label"><span id="alerts-dot" class="alerts-dot"></span>Alertas</div>
<div id="alerts" class="alerts-box"></div>
</div>
<script>
var MAX=__CACHE_MAX__;
function col(pct){return pct<50?'#4ade80':pct<80?'#fbbf24':'#f87171'}
function workerCol(mb){return mb<800?'#4ade80':mb<1000?'#fbbf24':'#f87171'}
function esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
function alertColor(s){return /sem reportar|taxa de erro/i.test(s)?'#d03b3b':'#fab219'}
function renderAlerts(alerts){
var box=document.getElementById('alerts'),dot=document.getElementById('alerts-dot');
if(!alerts||!alerts.length){
box.innerHTML='<div class="alert-empty">sem alertas</div>';
dot.className='alerts-dot';
return;
}
dot.className='alerts-dot warn';
box.innerHTML=alerts.map(function(a){
return '<div class="alert-line"><span style="color:'+alertColor(a)+'">●</span><span>'+esc(a)+'</span></div>';
}).join('');
}
function render(d){
var w=d.workers,m=d.machine,h=d.health;
var gb=(w.reduce(function(s,x){return s+x.rss_mb},0)/1024).toFixed(1);
var rc=col(m.ram_percent),cc=col(m.cpu_percent);
var ec=h.error_rate_pct>5?'#d03b3b':'#94a3b8';
document.getElementById('meta').innerHTML=
'<b>'+w.length+'</b> workers &nbsp;&middot;&nbsp; '+
'<b>'+gb+' GB</b> RSS &nbsp;&middot;&nbsp; '+
'RAM <b style="color:'+rc+'">'+m.ram_used_gb+'/'+m.ram_total_gb+' GB</b> &nbsp;&middot;&nbsp; '+
'CPU <b style="color:'+cc+'">'+m.cpu_percent+'%</b> &nbsp;&middot;&nbsp; '+
'<b>'+h.throughput_per_min+'</b> req/min &nbsp;&middot;&nbsp; '+
'<b style="color:'+ec+'">'+h.error_rate_pct+'%</b> erros &nbsp;&middot;&nbsp; '+
'<b>'+h.avg_latency_ms+'</b> ms &nbsp;&middot;&nbsp; '+
new Date().toTimeString().slice(0,8);
var maxRss=Math.max.apply(null,w.map(function(x){return x.rss_mb}).concat([1]));
document.getElementById('grid').innerHTML=w.map(function(x){
var c=workerCol(x.rss_mb),p=(x.rss_mb/maxRss*100).toFixed(0);
return '<div class="card" style="--c:'+c+'">'+
'<div class="card-top"><span class="pid" title="pid '+x.pid+'">'+(x.name||('pid '+x.pid))+'</span><span class="cn">'+x.cache_size+'/'+MAX+'</span></div>'+
'<div class="rss">'+x.rss_mb+'</div>'+
'<div class="card-bot"><span class="unit">MB</span><span class="req">'+((x.requests||0)+' req')+'</span></div>'+
'<div class="track"><div class="fill" style="width:'+p+'%;background:'+c+'"></div></div>'+
'</div>';
}).join('');
renderAlerts(d.alerts);
}
function refresh(){
var xhr=new XMLHttpRequest();
xhr.open('GET','/stats');
xhr.onload=function(){
if(xhr.status===200){
try{var d=JSON.parse(xhr.responseText);if(d&&d.workers&&d.machine&&d.health)render(d);}catch(e){}
}
};
xhr.send();
}
refresh();setInterval(refresh,5000);
</script>
</body></html>""").replace("__CACHE_MAX__", str(_CACHE_MAX))
@app.get("/stats", include_in_schema=False)
async def stats_endpoint():
data = get_dashboard_data()
data["machine"] = get_machine_stats()
return data
@app.get("/", include_in_schema=False)
async def root():
return HTMLResponse(content=_PAGE)
@app.post("/similarity", summary="Perform images similarity", response_model=List[ResponseModel], tags=["Similarities"])
async def similarity(images: List[RequestModel], quadrant: bool = True):
similarities = check_similarity(images, quadrant=quadrant)
return similarities