fix: use separate SQLite database for activity monitor

- ActivityMonitor now uses /data/activity_samples.sqlite instead of
  JupyterHub's main database to avoid SQLite locking conflicts
- Fixes "database is locked" errors that prevented login when both
  JupyterHub and ActivityMonitor wrote simultaneously
- Added "Last Active" column to activity table showing relative time
This commit is contained in:
stellarshenson
2026-01-20 19:04:39 +01:00
parent 66de0e7c5c
commit 4b2fc084bf
4 changed files with 37 additions and 3 deletions

View File

@@ -132,11 +132,16 @@ class ActivityMonitor:
return default
def _get_db(self):
"""Get or create database session"""
"""Get or create database session.
Uses a SEPARATE database file to avoid SQLite locking conflicts
with JupyterHub's main database.
"""
if self._db_session is not None:
return self._db_session
db_url = os.environ.get('JUPYTERHUB_DB_URL', 'sqlite:////data/jupyterhub.sqlite')
# Use separate database file to avoid locking conflicts with JupyterHub
db_url = 'sqlite:////data/activity_samples.sqlite'
try:
self._engine = create_engine(db_url)

View File

@@ -46,6 +46,7 @@
<th class="text-center">CPU</th>
<th class="text-center">Memory</th>
<th class="text-center">Time Left</th>
<th class="text-center">Last Active</th>
<th>Activity (7 days)</th>
</tr>
</thead>
@@ -166,6 +167,7 @@ require(["jquery"], function($) {
'<td class="text-center">' + formatCpu(user.cpu_percent) + '</td>' +
'<td class="text-center">' + formatMemory(user.memory_mb, user.memory_percent) + '</td>' +
'<td class="text-center">' + formatTimeRemaining(user.time_remaining_seconds) + '</td>' +
'<td class="text-center">' + formatLastActive(user.last_activity) + '</td>' +
'<td>' + renderActivityBar(user.activity_score) + '</td>' +
'</tr>';
tbody.append(row);
@@ -229,6 +231,30 @@ require(["jquery"], function($) {
}
}
function formatLastActive(isoString) {
if (!isoString) {
return '<span class="text-muted">--</span>';
}
var date = new Date(isoString);
var now = new Date();
var diffMs = now - date;
var diffSec = Math.floor(diffMs / 1000);
var diffMin = Math.floor(diffSec / 60);
var diffHour = Math.floor(diffMin / 60);
var remainingMin = diffMin % 60;
if (diffMin < 1) {
return '<span class="text-success">now</span>';
} else if (diffMin < 60) {
return diffMin + 'min';
} else if (diffHour < 24) {
return diffHour + 'h ' + remainingMin + 'min';
} else {
var diffDay = Math.floor(diffHour / 24);
return diffDay + 'd ' + (diffHour % 24) + 'h';
}
}
function renderActivityBar(score) {
if (score === null || score === undefined) {
return '<span class="text-muted small">--</span>';