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
37 changes: 29 additions & 8 deletions Lite/Analysis/AnomalyDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,12 @@ ORDER BY c.total_ms DESC
var currentMs = Convert.ToInt64(reader.GetValue(1));
var baselineMs = Convert.ToInt64(reader.GetValue(2));

// New wait (absent in baseline) or 5x+ increase
// Normalize to per-hour rates before comparing (windows are different lengths)
var baselineHours = (baselineEnd - baselineStart).TotalHours;
var currentHours = (context.TimeRangeEnd - context.TimeRangeStart).TotalHours;
if (baselineHours <= 0) baselineHours = 1;
if (currentHours <= 0) currentHours = 1;

double ratio;
string anomalyType;

Expand All @@ -271,7 +276,9 @@ ORDER BY c.total_ms DESC
}
else
{
ratio = (double)currentMs / baselineMs;
var baselineRate = baselineMs / baselineHours;
var currentRate = currentMs / currentHours;
ratio = baselineRate > 0 ? currentRate / baselineRate : 100.0;
anomalyType = "spike";
}

Expand Down Expand Up @@ -353,8 +360,22 @@ private async Task DetectBlockingAnomalies(AnalysisContext context,
var baselineDeadlocks = Convert.ToInt64(reader.GetValue(2));
var currentDeadlocks = Convert.ToInt64(reader.GetValue(3));

// Blocking spike: at least 5 events AND 3x baseline (or new)
if (currentBlocking >= 5 && (baselineBlocking == 0 || (double)currentBlocking / baselineBlocking >= 3))
// Normalize to per-hour rates (windows are different lengths)
var baselineHours = (baselineEnd - baselineStart).TotalHours;
var currentHours = (context.TimeRangeEnd - context.TimeRangeStart).TotalHours;
if (baselineHours <= 0) baselineHours = 1;
if (currentHours <= 0) currentHours = 1;

var baselineBlockingRate = baselineBlocking / baselineHours;
var currentBlockingRate = currentBlocking / currentHours;
var blockingRatio = baselineBlocking > 0 ? currentBlockingRate / baselineBlockingRate : 100.0;

var baselineDeadlockRate = baselineDeadlocks / baselineHours;
var currentDeadlockRate = currentDeadlocks / currentHours;
var deadlockRatio = baselineDeadlocks > 0 ? currentDeadlockRate / baselineDeadlockRate : 100.0;

// Blocking spike: at least 5 events AND 3x baseline rate (or new)
if (currentBlocking >= 5 && (baselineBlocking == 0 || blockingRatio >= 3))
{
anomalies.Add(new Fact
{
Expand All @@ -366,13 +387,13 @@ private async Task DetectBlockingAnomalies(AnalysisContext context,
{
["current_count"] = currentBlocking,
["baseline_count"] = baselineBlocking,
["ratio"] = baselineBlocking > 0 ? (double)currentBlocking / baselineBlocking : 100
["ratio"] = blockingRatio
}
});
}

// Deadlock spike: at least 3 events AND 3x baseline (or new)
if (currentDeadlocks >= 3 && (baselineDeadlocks == 0 || (double)currentDeadlocks / baselineDeadlocks >= 3))
// Deadlock spike: at least 3 events AND 3x baseline rate (or new)
if (currentDeadlocks >= 3 && (baselineDeadlocks == 0 || deadlockRatio >= 3))
{
anomalies.Add(new Fact
{
Expand All @@ -384,7 +405,7 @@ private async Task DetectBlockingAnomalies(AnalysisContext context,
{
["current_count"] = currentDeadlocks,
["baseline_count"] = baselineDeadlocks,
["ratio"] = baselineDeadlocks > 0 ? (double)currentDeadlocks / baselineDeadlocks : 100
["ratio"] = deadlockRatio
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion Lite/Controls/FinOpsTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ private async System.Threading.Tasks.Task LoadHighImpactQueriesAsync(int serverI
{
var hoursBack = GetHighImpactHoursBack();
var data = await _dataService.GetHighImpactQueriesAsync(serverId, hoursBack);
HighImpactDataGrid.ItemsSource = data;
_highImpactFilterMgr!.UpdateData(data);
HighImpactNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
HighImpactCountIndicator.Text = data.Count > 0 ? $"{data.Count} high-impact query(s)" : "";
}
Expand Down
7 changes: 4 additions & 3 deletions Lite/Controls/PlanViewerControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,16 +353,17 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1)
HorizontalAlignment = HorizontalAlignment.Center
});

// Actual rows of Estimated rows (accuracy %) — red if off by 10x+
// Actual rows per execution vs Estimated rows (accuracy %) — red if off by 10x+
var estRows = node.EstimateRows;
var accuracyRatio = estRows > 0 ? node.ActualRows / estRows : (node.ActualRows > 0 ? double.MaxValue : 1.0);
var actualRowsPerExec = node.ActualExecutions > 0 ? node.ActualRows / (double)node.ActualExecutions : node.ActualRows;
var accuracyRatio = estRows > 0 ? actualRowsPerExec / estRows : (actualRowsPerExec > 0 ? double.MaxValue : 1.0);
var rowBrush = (accuracyRatio < 0.1 || accuracyRatio > 10.0) ? Brushes.OrangeRed : fgBrush;
var accuracy = estRows > 0
? $" ({accuracyRatio * 100:F0}%)"
: "";
stack.Children.Add(new TextBlock
{
Text = $"{node.ActualRows:N0} of {estRows:N0}{accuracy}",
Text = $"{actualRowsPerExec:N0} of {estRows:N0}{accuracy}",
FontSize = 9,
Foreground = rowBrush,
TextAlignment = TextAlignment.Center,
Expand Down
3 changes: 2 additions & 1 deletion Lite/Services/LocalDataService.DailySummary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ FROM v_wait_stats
WHERE server_id = $1
AND collection_time >= $2 AND collection_time < $3
AND delta_wait_time_ms > 0
ORDER BY delta_wait_time_ms DESC
GROUP BY wait_type
ORDER BY SUM(delta_wait_time_ms) DESC
LIMIT 1
) AS top_wait_type,
COALESCE(
Expand Down
3 changes: 2 additions & 1 deletion Lite/Services/LocalDataService.WaitStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,12 @@ FROM v_wait_stats
WHERE server_id = $1
AND wait_type IN ('THREADPOOL', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE')
AND delta_waiting_tasks > 0
AND collection_time >= NOW() - INTERVAL '10 minutes'
AND collection_time >= $2
ORDER BY collection_time DESC
LIMIT 3";

command.Parameters.Add(new DuckDBParameter { Value = serverId });
command.Parameters.Add(new DuckDBParameter { Value = DateTime.UtcNow.AddMinutes(-10) });

var items = new List<PoisonWaitDelta>();
using var reader = await command.ExecuteReaderAsync();
Expand Down
37 changes: 24 additions & 13 deletions Lite/Windows/SettingsWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -861,25 +861,28 @@ private void ValidateSmtpButton_Click(object sender, RoutedEventArgs e)

private async void TestEmailButton_Click(object sender, RoutedEventArgs e)
{
/* Temporarily apply current UI values for the test */
App.SmtpServer = SmtpServerBox.Text?.Trim() ?? "";
if (int.TryParse(SmtpPortBox.Text, out var port))
App.SmtpPort = port;
App.SmtpUseSsl = SmtpSslCheckBox.IsChecked == true;
App.SmtpUsername = SmtpUsernameBox.Text?.Trim() ?? "";
App.SmtpFromAddress = SmtpFromBox.Text?.Trim() ?? "";
App.SmtpRecipients = SmtpRecipientsBox.Text?.Trim() ?? "";

if (!string.IsNullOrEmpty(SmtpPasswordBox.Password))
{
App.SaveSmtpPassword(SmtpPasswordBox.Password);
}
/* Save current App values so we can restore after the test */
var origServer = App.SmtpServer;
var origPort = App.SmtpPort;
var origSsl = App.SmtpUseSsl;
var origUsername = App.SmtpUsername;
var origFrom = App.SmtpFromAddress;
var origRecipients = App.SmtpRecipients;

TestEmailButton.IsEnabled = false;
TestEmailButton.Content = "Sending...";

try
{
/* Temporarily apply current UI values for the test */
App.SmtpServer = SmtpServerBox.Text?.Trim() ?? "";
if (int.TryParse(SmtpPortBox.Text, out var port))
App.SmtpPort = port;
App.SmtpUseSsl = SmtpSslCheckBox.IsChecked == true;
App.SmtpUsername = SmtpUsernameBox.Text?.Trim() ?? "";
App.SmtpFromAddress = SmtpFromBox.Text?.Trim() ?? "";
App.SmtpRecipients = SmtpRecipientsBox.Text?.Trim() ?? "";

var error = await Services.EmailAlertService.SendTestEmailAsync();
if (error == null)
{
Expand All @@ -892,6 +895,14 @@ private async void TestEmailButton_Click(object sender, RoutedEventArgs e)
}
finally
{
/* Restore original values — only Save should persist changes */
App.SmtpServer = origServer;
App.SmtpPort = origPort;
App.SmtpUseSsl = origSsl;
App.SmtpUsername = origUsername;
App.SmtpFromAddress = origFrom;
App.SmtpRecipients = origRecipients;

TestEmailButton.Content = "Send Test Email";
TestEmailButton.IsEnabled = true;
}
Expand Down
Loading