Spaces:
Running
Running
main - Reduce workers to 8, increase cache to 300, redesign monitoring page
Browse filesRemove /health endpoint and per-request MEM log; monitoring is now
internal only via background reporter. Root page redesigned with card
grid, colour-coded RSS (green/amber/red), cache progress bars and
server-side rendering with meta-refresh.
- Dockerfile +1 -1
- main.py +67 -28
- similarity.py +1 -5
Dockerfile
CHANGED
|
@@ -16,4 +16,4 @@ RUN python -c "from tensorflow.keras.applications import MobileNetV2; MobileNetV
|
|
| 16 |
COPY ./*.py /app/
|
| 17 |
|
| 18 |
|
| 19 |
-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "
|
|
|
|
| 16 |
COPY ./*.py /app/
|
| 17 |
|
| 18 |
|
| 19 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "8"]
|
main.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
from contextlib import asynccontextmanager
|
|
|
|
| 2 |
from typing import List
|
| 3 |
from fastapi import FastAPI
|
| 4 |
from fastapi.responses import HTMLResponse
|
|
@@ -16,42 +17,80 @@ async def lifespan(app: FastAPI):
|
|
| 16 |
|
| 17 |
app = FastAPI(title="Prozis Similarity Service", version="1.0.0", lifespan=lifespan)
|
| 18 |
|
|
|
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
@app.get("/", include_in_schema=False)
|
| 26 |
async def root():
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
"<link rel='preconnect' href='https://fonts.googleapis.com'>"
|
| 30 |
"<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>"
|
| 31 |
-
"<link href='https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@1,800&display=swap' rel='stylesheet'>"
|
| 32 |
-
"<
|
| 33 |
-
"
|
| 34 |
-
"
|
| 35 |
-
"
|
| 36 |
-
"
|
| 37 |
-
"
|
| 38 |
-
"
|
| 39 |
-
"
|
| 40 |
-
"
|
| 41 |
-
"
|
| 42 |
-
"
|
| 43 |
-
"
|
| 44 |
-
"
|
| 45 |
-
"
|
| 46 |
-
"
|
| 47 |
-
"
|
| 48 |
-
"
|
| 49 |
-
"}"
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
"</body></html>"
|
| 54 |
-
)
|
|
|
|
|
|
|
| 55 |
|
| 56 |
|
| 57 |
@app.post("/similarity", summary="Perform images similarity", response_model=List[ResponseModel], tags=["Similarities"])
|
|
|
|
| 1 |
from contextlib import asynccontextmanager
|
| 2 |
+
from datetime import datetime
|
| 3 |
from typing import List
|
| 4 |
from fastapi import FastAPI
|
| 5 |
from fastapi.responses import HTMLResponse
|
|
|
|
| 17 |
|
| 18 |
app = FastAPI(title="Prozis Similarity Service", version="1.0.0", lifespan=lifespan)
|
| 19 |
|
| 20 |
+
_CACHE_MAX = 300
|
| 21 |
|
| 22 |
+
|
| 23 |
+
def _rss_color(rss_mb: float) -> str:
|
| 24 |
+
if rss_mb < 800:
|
| 25 |
+
return "#22c55e"
|
| 26 |
+
if rss_mb < 1000:
|
| 27 |
+
return "#f59e0b"
|
| 28 |
+
return "#ef4444"
|
| 29 |
|
| 30 |
|
| 31 |
@app.get("/", include_in_schema=False)
|
| 32 |
async def root():
|
| 33 |
+
workers = get_all_memory_stats()
|
| 34 |
+
total_rss_gb = sum(w["rss_mb"] for w in workers) / 1024
|
| 35 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 36 |
+
|
| 37 |
+
cards = ""
|
| 38 |
+
for w in workers:
|
| 39 |
+
color = _rss_color(w["rss_mb"])
|
| 40 |
+
cache_pct = min(100, w["cache_size"] / _CACHE_MAX * 100)
|
| 41 |
+
cards += (
|
| 42 |
+
f"<div class='card' style='--c:{color}'>"
|
| 43 |
+
f"<div class='pid'>pid {w['pid']}</div>"
|
| 44 |
+
f"<div class='rss'>{w['rss_mb']}<span class='unit'> MB</span></div>"
|
| 45 |
+
f"<div class='bar-wrap'>"
|
| 46 |
+
f"<div class='bar-label'><span>cache</span><span>{w['cache_size']}/{_CACHE_MAX}</span></div>"
|
| 47 |
+
f"<div class='track'><div class='fill' style='width:{cache_pct:.0f}%'></div></div>"
|
| 48 |
+
f"</div></div>"
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
html = (
|
| 52 |
+
"<!DOCTYPE html><html><head>"
|
| 53 |
+
"<meta charset='UTF-8'>"
|
| 54 |
+
"<meta http-equiv='refresh' content='10'>"
|
| 55 |
+
"<title>Prozis Similarity Service</title>"
|
| 56 |
"<link rel='preconnect' href='https://fonts.googleapis.com'>"
|
| 57 |
"<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>"
|
| 58 |
+
"<link href='https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@1,800&family=Inter:wght@400;500&display=swap' rel='stylesheet'>"
|
| 59 |
+
"<style>"
|
| 60 |
+
"*{margin:0;padding:0;box-sizing:border-box}"
|
| 61 |
+
"body{background:#0d0d14;color:#e2e8f0;"
|
| 62 |
+
"font-family:'Inter',system-ui,sans-serif;min-height:100vh;padding:2rem 2.5rem}"
|
| 63 |
+
"h1{font-family:'Barlow Condensed',sans-serif;font-weight:800;font-style:italic;"
|
| 64 |
+
"font-size:2.4rem;color:#fff;letter-spacing:-.01em;line-height:1}"
|
| 65 |
+
".meta{margin-top:.5rem;color:#4b5563;font-size:.78rem;font-family:'SF Mono','Fira Code',monospace;letter-spacing:.03em}"
|
| 66 |
+
".meta b{color:#6b7280;font-weight:500}"
|
| 67 |
+
"header{margin-bottom:2rem;padding-bottom:1.5rem;border-bottom:1px solid #1a1a2e}"
|
| 68 |
+
".grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:.875rem}"
|
| 69 |
+
".card{background:#111827;border:1px solid #1e2435;border-radius:12px;padding:1.1rem 1.2rem;"
|
| 70 |
+
"position:relative;overflow:hidden;transition:border-color .15s,transform .15s}"
|
| 71 |
+
".card:hover{border-color:#2d3748;transform:translateY(-1px)}"
|
| 72 |
+
".card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:var(--c)}"
|
| 73 |
+
".pid{font-size:.62rem;color:#374151;font-family:'SF Mono','Fira Code',monospace;"
|
| 74 |
+
"text-transform:uppercase;letter-spacing:.1em;margin-bottom:.65rem}"
|
| 75 |
+
".rss{font-size:1.55rem;font-weight:700;font-family:'SF Mono','Fira Code',monospace;"
|
| 76 |
+
"color:var(--c);line-height:1;white-space:nowrap}"
|
| 77 |
+
".unit{font-size:.65rem;color:#6b7280;font-weight:400}"
|
| 78 |
+
".bar-wrap{margin-top:.9rem}"
|
| 79 |
+
".bar-label{font-size:.62rem;color:#374151;display:flex;justify-content:space-between;margin-bottom:.3rem}"
|
| 80 |
+
".track{background:#1a2235;border-radius:99px;height:3px;overflow:hidden}"
|
| 81 |
+
".fill{height:100%;border-radius:99px;background:#3b82f6;opacity:.75}"
|
| 82 |
+
"</style></head><body>"
|
| 83 |
+
f"<header>"
|
| 84 |
+
f"<h1>Prozis Similarity Service</h1>"
|
| 85 |
+
f"<div class='meta'>"
|
| 86 |
+
f"<b>{len(workers)}</b> workers · "
|
| 87 |
+
f"<b>{total_rss_gb:.1f} GB</b> RSS · {timestamp}"
|
| 88 |
+
f"</div></header>"
|
| 89 |
+
f"<div class='grid'>{cards}</div>"
|
| 90 |
"</body></html>"
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
return HTMLResponse(content=html)
|
| 94 |
|
| 95 |
|
| 96 |
@app.post("/similarity", summary="Perform images similarity", response_model=List[ResponseModel], tags=["Similarities"])
|
similarity.py
CHANGED
|
@@ -35,7 +35,7 @@ _mobilenet_lock = threading.Lock()
|
|
| 35 |
|
| 36 |
_image_cache = OrderedDict()
|
| 37 |
_image_cache_lock = threading.Lock()
|
| 38 |
-
_IMAGE_CACHE_MAX =
|
| 39 |
|
| 40 |
_process = psutil.Process()
|
| 41 |
|
|
@@ -394,8 +394,4 @@ def check_similarity(images: List[RequestModel], quadrant: bool = True):
|
|
| 394 |
for i in range(1, len(images))
|
| 395 |
]
|
| 396 |
|
| 397 |
-
memory_stats = get_memory_stats()
|
| 398 |
-
logging.info(f"[MEM] pid={memory_stats['pid']} rss={memory_stats['rss_mb']}MB "
|
| 399 |
-
f"cache_size={memory_stats['cache_size']} images_in_request={len(images) - 1}")
|
| 400 |
-
|
| 401 |
return results
|
|
|
|
| 35 |
|
| 36 |
_image_cache = OrderedDict()
|
| 37 |
_image_cache_lock = threading.Lock()
|
| 38 |
+
_IMAGE_CACHE_MAX = 300
|
| 39 |
|
| 40 |
_process = psutil.Process()
|
| 41 |
|
|
|
|
| 394 |
for i in range(1, len(images))
|
| 395 |
]
|
| 396 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
return results
|