mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
better maintanance funcion with catch-up
This commit is contained in:
@@ -63,7 +63,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
try
|
||||
{
|
||||
await MarkStalledSessionsAsTimedOutAsync();
|
||||
await AggregateYesterdayMetricsAsync();
|
||||
await AggregateMetricsWithCatchUpAsync();
|
||||
await CleanupOldDataAsync();
|
||||
await RecomputePlatformStatsAsync();
|
||||
|
||||
@@ -146,10 +146,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
var timeoutHours = config?.SessionTimeoutHours ?? 5;
|
||||
var cutoffTime = DateTime.UtcNow.AddHours(-timeoutHours);
|
||||
|
||||
var sessions = await _storageService.GetSessionsAsync(tenantId, maxResults: 1000000);
|
||||
var stalledSessions = sessions
|
||||
.Where(s => s.Status == SessionStatus.InProgress && s.StartedAt < cutoffTime)
|
||||
.ToList();
|
||||
var stalledSessions = await _storageService.GetStalledSessionsAsync(tenantId, cutoffTime);
|
||||
|
||||
if (stalledSessions.Count == 0)
|
||||
{
|
||||
@@ -203,12 +200,39 @@ namespace AutopilotMonitor.Functions.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggregates yesterday's metrics and saves them as historical snapshots
|
||||
/// Aggregates metrics for any missed days in the last 7 days, plus yesterday.
|
||||
/// Checks the UsageMetrics table for existing snapshots to avoid re-aggregation.
|
||||
/// </summary>
|
||||
private async Task AggregateYesterdayMetricsAsync()
|
||||
private async Task AggregateMetricsWithCatchUpAsync()
|
||||
{
|
||||
var yesterday = DateTime.UtcNow.AddDays(-1).Date;
|
||||
await AggregateMetricsForDateAsync(yesterday);
|
||||
const int maxCatchUpDays = 7;
|
||||
var today = DateTime.UtcNow.Date;
|
||||
var aggregatedCount = 0;
|
||||
|
||||
for (int daysBack = maxCatchUpDays; daysBack >= 1; daysBack--)
|
||||
{
|
||||
var date = today.AddDays(-daysBack);
|
||||
var dateStr = date.ToString("yyyy-MM-dd");
|
||||
|
||||
try
|
||||
{
|
||||
if (await _storageService.HasUsageMetricsSnapshotAsync(dateStr))
|
||||
continue;
|
||||
|
||||
_logger.LogInformation($"Catch-up: Aggregating metrics for missed date {dateStr}");
|
||||
await AggregateMetricsForDateAsync(date);
|
||||
aggregatedCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Failed to aggregate metrics for {dateStr} during catch-up");
|
||||
}
|
||||
}
|
||||
|
||||
if (aggregatedCount > 0)
|
||||
_logger.LogInformation($"Catch-up completed: aggregated {aggregatedCount} missed day(s)");
|
||||
else
|
||||
_logger.LogInformation("No missed days to catch up on");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -223,8 +247,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
{
|
||||
var targetDateStr = targetDate.ToString("yyyy-MM-dd");
|
||||
|
||||
var allSessions = await _storageService.GetAllSessionsAsync(maxResults: 1000000);
|
||||
var targetDateSessions = allSessions.Where(s => s.StartedAt.Date == targetDate).ToList();
|
||||
var targetDateSessions = await _storageService.GetSessionsByDateRangeAsync(targetDate, targetDate.AddDays(1));
|
||||
|
||||
if (targetDateSessions.Count == 0)
|
||||
{
|
||||
|
||||
@@ -798,6 +798,29 @@ namespace AutopilotMonitor.Functions.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a global usage metrics snapshot exists for a given date.
|
||||
/// Used by maintenance catch-up to determine which dates need aggregation.
|
||||
/// </summary>
|
||||
public async Task<bool> HasUsageMetricsSnapshotAsync(string date)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.UsageMetrics);
|
||||
await tableClient.GetEntityAsync<TableEntity>(date, "global");
|
||||
return true;
|
||||
}
|
||||
catch (RequestFailedException ex) when (ex.Status == 404)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Failed to check usage metrics snapshot for {date}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== DATA RETENTION METHODS =====
|
||||
|
||||
/// <summary>
|
||||
@@ -831,6 +854,72 @@ namespace AutopilotMonitor.Functions.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all sessions within a date range, optionally filtered by tenant.
|
||||
/// Uses server-side filtering to avoid loading all sessions into memory.
|
||||
/// </summary>
|
||||
public async Task<List<SessionSummary>> GetSessionsByDateRangeAsync(DateTime startDate, DateTime endDate, string? tenantId = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(tenantId))
|
||||
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
|
||||
|
||||
try
|
||||
{
|
||||
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Sessions);
|
||||
|
||||
var filter = !string.IsNullOrEmpty(tenantId)
|
||||
? $"PartitionKey eq '{tenantId}' and StartedAt ge datetime'{startDate:yyyy-MM-ddTHH:mm:ss}Z' and StartedAt lt datetime'{endDate:yyyy-MM-ddTHH:mm:ss}Z'"
|
||||
: $"StartedAt ge datetime'{startDate:yyyy-MM-ddTHH:mm:ss}Z' and StartedAt lt datetime'{endDate:yyyy-MM-ddTHH:mm:ss}Z'";
|
||||
|
||||
var query = tableClient.QueryAsync<TableEntity>(filter: filter);
|
||||
|
||||
var sessions = new List<SessionSummary>();
|
||||
await foreach (var entity in query)
|
||||
{
|
||||
sessions.Add(MapToSessionSummary(entity));
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Found {sessions.Count} sessions between {startDate:yyyy-MM-dd} and {endDate:yyyy-MM-dd}" +
|
||||
(tenantId != null ? $" for tenant {tenantId}" : ""));
|
||||
return sessions;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Failed to get sessions by date range");
|
||||
return new List<SessionSummary>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets stalled sessions (InProgress status, started before cutoff time) for a tenant.
|
||||
/// Uses server-side filtering to avoid loading all sessions into memory.
|
||||
/// </summary>
|
||||
public async Task<List<SessionSummary>> GetStalledSessionsAsync(string tenantId, DateTime cutoffTime)
|
||||
{
|
||||
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
|
||||
|
||||
try
|
||||
{
|
||||
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Sessions);
|
||||
|
||||
var filter = $"PartitionKey eq '{tenantId}' and Status eq 'InProgress' and StartedAt lt datetime'{cutoffTime:yyyy-MM-ddTHH:mm:ss}Z'";
|
||||
var query = tableClient.QueryAsync<TableEntity>(filter: filter);
|
||||
|
||||
var sessions = new List<SessionSummary>();
|
||||
await foreach (var entity in query)
|
||||
{
|
||||
sessions.Add(MapToSessionSummary(entity));
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Failed to get stalled sessions for tenant {tenantId}");
|
||||
return new List<SessionSummary>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique tenant IDs from sessions table
|
||||
/// </summary>
|
||||
|
||||
@@ -45,6 +45,7 @@ export default function AdminConfigurationPage() {
|
||||
const { getAccessToken } = useAuth();
|
||||
const [galacticAdminMode, setGalacticAdminMode] = useState(false);
|
||||
const [triggeringMaintenance, setTriggeringMaintenance] = useState(false);
|
||||
const [maintenanceDate, setMaintenanceDate] = useState<string>("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||
|
||||
@@ -172,7 +173,8 @@ export default function AdminConfigurationPage() {
|
||||
throw new Error('Failed to get access token');
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/api/maintenance/trigger`, {
|
||||
const queryParams = maintenanceDate ? `?date=${maintenanceDate}` : '';
|
||||
const response = await fetch(`${API_BASE_URL}/api/maintenance/trigger${queryParams}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
@@ -184,7 +186,8 @@ export default function AdminConfigurationPage() {
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
setSuccessMessage("Daily maintenance job completed successfully!");
|
||||
const dateInfo = maintenanceDate ? ` for ${maintenanceDate}` : '';
|
||||
setSuccessMessage(`Maintenance job completed successfully${dateInfo}!`);
|
||||
|
||||
// Auto-hide success message after 5 seconds
|
||||
setTimeout(() => setSuccessMessage(null), 5000);
|
||||
@@ -665,13 +668,29 @@ export default function AdminConfigurationPage() {
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-purple-500 mr-2">•</span>
|
||||
<span>Aggregate yesterday's metrics into historical snapshots</span>
|
||||
<span>Aggregate metrics into historical snapshots (with automatic catch-up for missed days)</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-purple-500 mr-2">•</span>
|
||||
<span>Clean up old data based on retention policies</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4 mb-4">
|
||||
<label className="block text-sm font-medium text-purple-800 mb-1">
|
||||
Target Date (optional)
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={maintenanceDate}
|
||||
onChange={(e) => setMaintenanceDate(e.target.value)}
|
||||
max={new Date(Date.now() - 86400000).toISOString().split('T')[0]}
|
||||
className="w-full max-w-xs px-3 py-2 border border-purple-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
Leave empty to run the standard maintenance with automatic catch-up (aggregates any missed days within the last 7 days).
|
||||
Select a specific date to manually aggregate metrics for that day, e.g. to backfill data older than 7 days.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-4">
|
||||
<div className="flex items-start space-x-2">
|
||||
<svg className="w-5 h-5 text-yellow-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user