Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This page covers common Docker workflows and troubleshooting.

Simons typical workflow.

While making code changes, I do this.
While making code changes, I use `./rebuild.sh`, that does this:

```bash
docker compose down --remove-orphans
Expand Down
44 changes: 44 additions & 0 deletions frontend_multi_user/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1971,6 +1971,41 @@ def _build_plan_telemetry(

return telemetry

def _get_database_size_info(self) -> dict[str, Any]:
"""Query PostgreSQL for database size and per-table breakdown."""
from sqlalchemy import text
info: dict[str, Any] = {"error": None, "database_name": None, "total_bytes": 0, "total_mb": 0.0, "tables": []}
try:
with self.db.engine.connect() as conn:
row = conn.execute(text(
"SELECT current_database(), pg_database_size(current_database())"
)).fetchone()
if row:
info["database_name"] = row[0]
info["total_bytes"] = row[1]
info["total_mb"] = round(row[1] / (1024 * 1024), 2)

tables = conn.execute(text(
"SELECT schemaname, tablename, "
"pg_total_relation_size(schemaname || '.' || tablename) AS total_bytes, "
"pg_relation_size(schemaname || '.' || tablename) AS table_bytes, "
"pg_total_relation_size(schemaname || '.' || tablename) - pg_relation_size(schemaname || '.' || tablename) AS index_bytes "
"FROM pg_tables WHERE schemaname = 'public' "
"ORDER BY total_bytes DESC"
)).fetchall()
for t in tables:
info["tables"].append({
"name": t[1],
"total_bytes": t[2],
"total_mb": round(t[2] / (1024 * 1024), 2),
"table_mb": round(t[3] / (1024 * 1024), 2),
"index_mb": round(t[4] / (1024 * 1024), 2),
})
except Exception as e:
logger.exception("Failed to query database size")
info["error"] = str(e)
return info

def _build_reconciliation_report(self, max_tasks: int, tolerance_usd: float) -> tuple[list[dict[str, Any]], dict[str, Any]]:
tasks = (
PlanItem.query
Expand Down Expand Up @@ -2942,6 +2977,15 @@ def admin_reconciliation():
refresh_seconds=refresh_seconds,
)

@self.app.route('/admin/db-size')
@admin_required
def admin_db_size():
size_info = self._get_database_size_info()
return self.admin.index_view.render(
"admin/db_size.html",
size_info=size_info,
)

@self.app.route('/ping/stream')
@login_required
def ping_stream():
Expand Down
121 changes: 121 additions & 0 deletions frontend_multi_user/templates/admin/db_size.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{% extends 'admin/master.html' %}

{% block head_css %}
{{ super() }}
<style>
.dbsize-wrap {
max-width: 900px;
margin: 0 auto;
padding: 1.5rem;
color: #222;
}
.dbsize-total {
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
padding: 1.25rem 1.5rem;
margin-bottom: 1.5rem;
display: flex;
gap: 2rem;
align-items: baseline;
}
.dbsize-total .big-number {
font-size: 2rem;
font-weight: 700;
color: #2c3e50;
}
.dbsize-total .label {
color: #666;
font-size: 0.95rem;
}
table.dbsize-table {
width: 100%;
border-collapse: collapse;
background: #fff;
}
.dbsize-table th,
.dbsize-table td {
border: 1px solid #ddd;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
}
.dbsize-table th {
background: #f7f7f7;
text-align: left;
}
.dbsize-table td.num {
text-align: right;
font-family: monospace;
}
.bar-cell {
width: 120px;
}
.bar {
height: 14px;
background: #3498db;
border-radius: 3px;
min-width: 1px;
}
.error-banner {
margin: 1rem 0;
padding: 0.75rem 1rem;
border-radius: 6px;
border: 1px solid #e0b4b4;
background: #fff6f6;
color: #8a1f1f;
font-weight: 600;
}
</style>
{% endblock %}

{% block body %}
<div class="dbsize-wrap">
<h2>Database Size</h2>

{% if size_info.error %}
<div class="error-banner">
Error querying database size: {{ size_info.error }}
</div>
{% else %}
<div class="dbsize-total">
<div>
<div class="big-number">{{ size_info.total_mb }} MB</div>
<div class="label">Total database size</div>
</div>
<div>
<div class="label">Database: <strong>{{ size_info.database_name }}</strong></div>
</div>
</div>

{% if size_info.tables %}
<h3>Per-table breakdown</h3>
<table class="dbsize-table">
<thead>
<tr>
<th>Table</th>
<th>Total (MB)</th>
<th>Data (MB)</th>
<th>Indexes (MB)</th>
<th class="bar-cell"></th>
</tr>
</thead>
<tbody>
{% for t in size_info.tables %}
<tr>
<td>{{ t.name }}</td>
<td class="num">{{ t.total_mb }}</td>
<td class="num">{{ t.table_mb }}</td>
<td class="num">{{ t.index_mb }}</td>
<td class="bar-cell">
{% if size_info.tables[0].total_bytes > 0 %}
<div class="bar" style="width: {{ (t.total_bytes / size_info.tables[0].total_bytes * 100) | int }}%"></div>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endif %}
</div>
{% endblock %}
4 changes: 4 additions & 0 deletions frontend_multi_user/templates/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,9 @@ <h2>Primary links</h2>
Reconciliation and drift detection
<div class="description">Compare billed usage cost vs tracked inference cost per task.</div>
</a>
<a href="/admin/db-size">
Database Size
<div class="description">View PostgreSQL disk space usage and per-table breakdown.</div>
</a>
</div>
{% endblock %}
5 changes: 5 additions & 0 deletions rebuild.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
docker compose down --remove-orphans
docker compose build --no-cache
docker compose up
Loading