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
5 changes: 3 additions & 2 deletions Dashboard/Services/DatabaseService.NocHealth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using PerformanceMonitorDashboard.Helpers;
Expand Down Expand Up @@ -631,7 +632,7 @@ ORDER BY r.total_elapsed_time DESC
{
using var cmd = new SqlCommand(query, connection);
cmd.CommandTimeout = 10;
cmd.Parameters.AddWithValue("@thresholdMs", (long)thresholdMinutes * 60 * 1000);
cmd.Parameters.Add(new SqlParameter("@thresholdMs", SqlDbType.BigInt) { Value = (long)thresholdMinutes * 60 * 1000 });
using var reader = await cmd.ExecuteReaderAsync();

while (await reader.ReadAsync())
Expand Down Expand Up @@ -685,7 +686,7 @@ ORDER BY percent_of_average DESC
{
using var cmd = new SqlCommand(query, connection);
cmd.CommandTimeout = 10;
cmd.Parameters.AddWithValue("@thresholdPercent", thresholdPercent);
cmd.Parameters.Add(new SqlParameter("@thresholdPercent", SqlDbType.Int) { Value = thresholdPercent });
using var reader = await cmd.ExecuteReaderAsync();

while (await reader.ReadAsync())
Expand Down
9 changes: 5 additions & 4 deletions Dashboard/Services/DatabaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -302,10 +303,10 @@ public async Task UpdateCollectorScheduleAsync(int scheduleId, bool enabled, int

using var command = new SqlCommand(query, connection);
command.CommandTimeout = 120;
command.Parameters.AddWithValue("@schedule_id", scheduleId);
command.Parameters.AddWithValue("@enabled", enabled);
command.Parameters.AddWithValue("@frequency_minutes", frequencyMinutes);
command.Parameters.AddWithValue("@retention_days", retentionDays);
command.Parameters.Add(new SqlParameter("@schedule_id", SqlDbType.Int) { Value = scheduleId });
command.Parameters.Add(new SqlParameter("@enabled", SqlDbType.Bit) { Value = enabled });
command.Parameters.Add(new SqlParameter("@frequency_minutes", SqlDbType.Int) { Value = frequencyMinutes });
command.Parameters.Add(new SqlParameter("@retention_days", SqlDbType.Int) { Value = retentionDays });

await command.ExecuteNonQueryAsync();
}
Expand Down
21 changes: 11 additions & 10 deletions Installer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net.Http;
Expand Down Expand Up @@ -1341,16 +1342,16 @@ INSERT INTO PerformanceMonitor.config.installation_history

using (var insertCmd = new SqlCommand(insertSql, connection))
{
insertCmd.Parameters.AddWithValue("@installer_version", assemblyVersion);
insertCmd.Parameters.AddWithValue("@installer_info_version", (object?)infoVersion ?? DBNull.Value);
insertCmd.Parameters.AddWithValue("@sql_server_version", sqlVersion);
insertCmd.Parameters.AddWithValue("@sql_server_edition", sqlEdition);
insertCmd.Parameters.AddWithValue("@installation_type", installationType);
insertCmd.Parameters.AddWithValue("@previous_version", (object?)previousVersion ?? DBNull.Value);
insertCmd.Parameters.AddWithValue("@installation_status", status);
insertCmd.Parameters.AddWithValue("@files_executed", filesExecuted);
insertCmd.Parameters.AddWithValue("@files_failed", filesFailed);
insertCmd.Parameters.AddWithValue("@installation_duration_ms", durationMs);
insertCmd.Parameters.Add(new SqlParameter("@installer_version", SqlDbType.NVarChar, 50) { Value = assemblyVersion });
insertCmd.Parameters.Add(new SqlParameter("@installer_info_version", SqlDbType.NVarChar, 100) { Value = (object?)infoVersion ?? DBNull.Value });
insertCmd.Parameters.Add(new SqlParameter("@sql_server_version", SqlDbType.NVarChar, 500) { Value = sqlVersion });
insertCmd.Parameters.Add(new SqlParameter("@sql_server_edition", SqlDbType.NVarChar, 128) { Value = sqlEdition });
insertCmd.Parameters.Add(new SqlParameter("@installation_type", SqlDbType.VarChar, 20) { Value = installationType });
insertCmd.Parameters.Add(new SqlParameter("@previous_version", SqlDbType.NVarChar, 50) { Value = (object?)previousVersion ?? DBNull.Value });
insertCmd.Parameters.Add(new SqlParameter("@installation_status", SqlDbType.VarChar, 20) { Value = status });
insertCmd.Parameters.Add(new SqlParameter("@files_executed", SqlDbType.Int) { Value = filesExecuted });
insertCmd.Parameters.Add(new SqlParameter("@files_failed", SqlDbType.Int) { Value = filesFailed });
insertCmd.Parameters.Add(new SqlParameter("@installation_duration_ms", SqlDbType.BigInt) { Value = durationMs });

await insertCmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
Expand Down
32 changes: 23 additions & 9 deletions Lite/Controls/ServerTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,8 @@ Include the latest event timestamp so acknowledgement is only
DateTime? latestEventTime = null;
if (blockingCount > 0 || deadlockCount > 0)
{
var latestBlocking = blockedProcessTask.Result.Max(r => (DateTime?)r.CollectionTime);
var latestDeadlock = deadlockTask.Result.Max(r => (DateTime?)r.CollectionTime);
var latestBlocking = blockedProcessTask.Result.Max(r => (DateTime?)r.EventTime);
var latestDeadlock = deadlockTask.Result.Max(r => (DateTime?)r.DeadlockTime);
latestEventTime = latestBlocking > latestDeadlock ? latestBlocking : latestDeadlock;
}
AlertCountsChanged?.Invoke(blockingCount, deadlockCount, latestEventTime);
Expand Down Expand Up @@ -849,12 +849,19 @@ private void UpdateBlockingTrendChart(List<TrendPoint> data, int hoursBack, Date

if (data.Count == 0)
{
/* Show empty chart with correct time range */
/* No blocking events — show a flat line at zero so the chart looks active */
var zeroLine = BlockingTrendChart.Plot.Add.Scatter(
new[] { rangeStart.ToOADate(), rangeEnd.ToOADate() },
new[] { 0.0, 0.0 });
zeroLine.LegendText = "Blocking Incidents";
zeroLine.Color = ScottPlot.Color.FromHex("#E57373");
zeroLine.MarkerSize = 0;
BlockingTrendChart.Plot.Axes.DateTimeTicksBottom();
BlockingTrendChart.Plot.Axes.SetLimitsX(rangeStart.ToOADate(), rangeEnd.ToOADate());
BlockingTrendChart.Plot.Axes.SetLimitsY(0, 1);
ReapplyAxisColors(BlockingTrendChart);
BlockingTrendChart.Plot.YLabel("Blocking Incidents");
SetChartYLimitsWithLegendPadding(BlockingTrendChart, 0, 1);
ShowChartLegend(BlockingTrendChart);
BlockingTrendChart.Refresh();
return;
}
Expand Down Expand Up @@ -919,12 +926,19 @@ private void UpdateDeadlockTrendChart(List<TrendPoint> data, int hoursBack, Date

if (data.Count == 0)
{
/* Show empty chart with correct time range */
/* No deadlocks — show a flat line at zero so the chart looks active */
var zeroLine = DeadlockTrendChart.Plot.Add.Scatter(
new[] { rangeStart.ToOADate(), rangeEnd.ToOADate() },
new[] { 0.0, 0.0 });
zeroLine.LegendText = "Deadlocks";
zeroLine.Color = ScottPlot.Color.FromHex("#FFB74D");
zeroLine.MarkerSize = 0;
DeadlockTrendChart.Plot.Axes.DateTimeTicksBottom();
DeadlockTrendChart.Plot.Axes.SetLimitsX(rangeStart.ToOADate(), rangeEnd.ToOADate());
DeadlockTrendChart.Plot.Axes.SetLimitsY(0, 1);
ReapplyAxisColors(DeadlockTrendChart);
DeadlockTrendChart.Plot.YLabel("Deadlocks");
SetChartYLimitsWithLegendPadding(DeadlockTrendChart, 0, 1);
ShowChartLegend(DeadlockTrendChart);
DeadlockTrendChart.Refresh();
return;
}
Expand Down Expand Up @@ -1495,13 +1509,13 @@ private static void SetChartYLimitsWithLegendPadding(ScottPlot.WPF.WpfPlot chart
dataYMin = limits.Bottom;
dataYMax = limits.Top;
}
if (dataYMax <= dataYMin) dataYMax = dataYMin + 100;
if (dataYMax <= dataYMin) dataYMax = dataYMin + 1;

double range = dataYMax - dataYMin;
double topPadding = range * 0.05;

/* Only add bottom padding if dataYMin is above zero - don't go negative */
double yMin = dataYMin >= 0 ? 0 : dataYMin - (range * 0.10);
/* Add small bottom margin when dataYMin is zero so flat lines at Y=0 are visible above the axis */
double yMin = dataYMin > 0 ? 0 : dataYMin == 0 ? -(range * 0.05) : dataYMin - (range * 0.10);
double yMax = dataYMax + topPadding;

chart.Plot.Axes.SetLimitsY(yMin, yMax);
Expand Down
17 changes: 16 additions & 1 deletion Lite/Database/DuckDbInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class DuckDbInitializer
/// <summary>
/// Current schema version. Increment this when schema changes require table rebuilds.
/// </summary>
private const int CurrentSchemaVersion = 9;
private const int CurrentSchemaVersion = 10;

public DuckDbInitializer(string databasePath, ILogger<DuckDbInitializer>? logger = null)
{
Expand Down Expand Up @@ -328,6 +328,21 @@ Safe to ALTER because this table uses INSERT (not appender). */
/* Table doesn't exist yet — will be created with correct schema below */
}
}

if (fromVersion < 10)
{
/* v10: Added server_name column to collection_log so log entries
can be identified by server without needing a lookup table. */
_logger?.LogInformation("Running migration to v10: adding server_name column to collection_log");
try
{
await ExecuteNonQueryAsync(connection, "ALTER TABLE collection_log ADD COLUMN IF NOT EXISTS server_name VARCHAR");
}
catch
{
/* Table doesn't exist yet — will be created with correct schema below */
}
}
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions Lite/Database/Schema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
CREATE TABLE IF NOT EXISTS collection_log (
log_id BIGINT PRIMARY KEY,
server_id INTEGER NOT NULL,
server_name VARCHAR,
collector_name VARCHAR NOT NULL,
collection_time TIMESTAMP NOT NULL,
duration_ms INTEGER,
Expand Down
4 changes: 2 additions & 2 deletions Lite/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="{DynamicResource ForegroundMutedBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<DataTrigger Binding="{Binding IsOnline}" Value="True">
<Setter Property="Fill" Value="{DynamicResource SuccessBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<DataTrigger Binding="{Binding IsOnline}" Value="False">
<Setter Property="Fill" Value="{DynamicResource ErrorBrush}"/>
</DataTrigger>
</Style.Triggers>
Expand Down
18 changes: 17 additions & 1 deletion Lite/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ private void ServerTabControl_SelectionChanged(object sender, SelectionChangedEv
private void RefreshServerList()
{
var servers = _serverManager.GetAllServers();
foreach (var server in servers)
{
server.IsOnline = _serverManager.GetConnectionStatus(server.Id).IsOnline;
}
ServerListView.ItemsSource = servers;

// Update UI based on server count
Expand Down Expand Up @@ -883,9 +887,11 @@ private void CheckConnectionsAndNotify()
try
{
var servers = _serverManager.GetAllServers();
bool needsRefresh = false;
foreach (var server in servers)
{
var status = _serverManager.GetConnectionStatus(server.Id);
server.IsOnline = status?.IsOnline;
if (status?.IsOnline == null) continue;

bool isOnline = status.IsOnline == true;
Expand All @@ -912,12 +918,22 @@ private void CheckConnectionsAndNotify()

if (wasOnline != isOnline)
{
RefreshServerList();
needsRefresh = true;
}
}
else
{
/* First time seeing this server's status — need to refresh */
needsRefresh = true;
}

_previousConnectionStates[server.Id] = isOnline;
}

if (needsRefresh)
{
RefreshServerList();
}
}
catch (Exception ex)
{
Expand Down
7 changes: 7 additions & 0 deletions Lite/Models/ServerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ public bool UseWindowsAuth
_ => "Windows"
};

/// <summary>
/// Actual connection status from the most recent connection check.
/// null = not checked yet, true = online, false = offline.
/// </summary>
[JsonIgnore]
public bool? IsOnline { get; set; }

/// <summary>
/// Display-only property for showing status in UI.
/// </summary>
Expand Down
7 changes: 5 additions & 2 deletions Lite/Services/LocalDataService.CollectionHealth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ public async Task<List<CollectionLogRow>> GetRecentCollectionLogAsync(int server
duckdb_duration_ms,
rows_collected,
status,
error_message
error_message,
server_name
FROM collection_log
WHERE server_id = $1
AND collection_time >= $2
Expand All @@ -103,7 +104,8 @@ ORDER BY collection_time DESC
DuckDbDurationMs = reader.IsDBNull(4) ? null : (int?)Convert.ToInt32(reader.GetValue(4)),
RowsCollected = reader.IsDBNull(5) ? null : (int?)Convert.ToInt32(reader.GetValue(5)),
Status = reader.GetString(6),
ErrorMessage = reader.IsDBNull(7) ? null : reader.GetString(7)
ErrorMessage = reader.IsDBNull(7) ? null : reader.GetString(7),
ServerName = reader.IsDBNull(8) ? null : reader.GetString(8)
});
}

Expand All @@ -114,6 +116,7 @@ ORDER BY collection_time DESC
public class CollectionLogRow
{
public string CollectorName { get; set; } = "";
public string? ServerName { get; set; }
public DateTime CollectionTime { get; set; }
public int? DurationMs { get; set; }
public int? SqlDurationMs { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion Lite/Services/LocalDataService.Overview.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ FROM blocked_process_reports
SELECT COUNT(*)
FROM deadlocks
WHERE server_id = $1
AND collection_time >= $2";
AND deadlock_time >= $2";
cmd.Parameters.Add(new DuckDBParameter { Value = serverId });
cmd.Parameters.Add(new DuckDBParameter { Value = DateTime.UtcNow.AddHours(-1) });
var result = await cmd.ExecuteScalarAsync();
Expand Down
9 changes: 5 additions & 4 deletions Lite/Services/LocalDataService.QueryStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using DuckDB.NET.Data;
using Microsoft.Data.SqlClient;
Expand All @@ -27,7 +28,7 @@ public partial class LocalDataService
quoted_name = QUOTENAME(d.name)
FROM sys.databases AS d
WHERE d.name = @database_name;", connection);
command.Parameters.AddWithValue("@database_name", databaseName);
command.Parameters.Add(new SqlParameter("@database_name", SqlDbType.NVarChar, 128) { Value = databaseName });
var result = await command.ExecuteScalarAsync();
return result as string;
}
Expand Down Expand Up @@ -247,7 +248,7 @@ qs.total_elapsed_time DESC
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
using var command = new SqlCommand(query, connection) { CommandTimeout = 30 };
command.Parameters.AddWithValue("@query_hash", queryHash);
command.Parameters.Add(new SqlParameter("@query_hash", SqlDbType.VarChar, 64) { Value = queryHash });
var result = await command.ExecuteScalarAsync();
return result as string;
}
Expand Down Expand Up @@ -294,8 +295,8 @@ ps.total_elapsed_time DESC
@schema_name;";

using var command = new SqlCommand(query, connection) { CommandTimeout = 30 };
command.Parameters.AddWithValue("@object_name", objectName);
command.Parameters.AddWithValue("@schema_name", schemaName);
command.Parameters.Add(new SqlParameter("@object_name", SqlDbType.NVarChar, 128) { Value = objectName });
command.Parameters.Add(new SqlParameter("@schema_name", SqlDbType.NVarChar, 128) { Value = schemaName });
var result = await command.ExecuteScalarAsync();
return result as string;
}
Expand Down
3 changes: 2 additions & 1 deletion Lite/Services/LocalDataService.QueryStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using DuckDB.NET.Data;
using Microsoft.Data.SqlClient;
Expand Down Expand Up @@ -198,7 +199,7 @@ AND qsp.query_plan IS NOT NULL
@plan_id;";

using var command = new SqlCommand(query, connection) { CommandTimeout = 30 };
command.Parameters.AddWithValue("@plan_id", planId);
command.Parameters.Add(new SqlParameter("@plan_id", SqlDbType.BigInt) { Value = planId });
var result = await command.ExecuteScalarAsync();
return result as string;
}
Expand Down
8 changes: 4 additions & 4 deletions Lite/Services/RemoteCollectorService.BlockedProcessReport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

using System;
using System.Data;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -126,7 +127,7 @@ LEFT JOIN sys.dm_xe_sessions AS dxs
WHERE ses.name = @session_name;", connection))
{
cmd.CommandTimeout = CommandTimeoutSeconds;
cmd.Parameters.AddWithValue("@session_name", BlockedProcessXeSessionName);
cmd.Parameters.Add(new SqlParameter("@session_name", SqlDbType.NVarChar, 128) { Value = BlockedProcessXeSessionName });
var result = await cmd.ExecuteScalarAsync(cancellationToken);

if (result != null)
Expand Down Expand Up @@ -205,7 +206,7 @@ FROM sys.database_event_sessions AS des
WHERE des.name = @session_name;", connection))
{
cmd.CommandTimeout = CommandTimeoutSeconds;
cmd.Parameters.AddWithValue("@session_name", BlockedProcessXeSessionName);
cmd.Parameters.Add(new SqlParameter("@session_name", SqlDbType.NVarChar, 128) { Value = BlockedProcessXeSessionName });
var result = await cmd.ExecuteScalarAsync(cancellationToken);

if (result != null)
Expand Down Expand Up @@ -380,8 +381,7 @@ as it lingers in the ring buffer across collection cycles. */
command.CommandTimeout = CommandTimeoutSeconds;

/* Use the most recent timestamp from DuckDB as the cutoff, or fall back to 10-minute window */
command.Parameters.AddWithValue("@cutoff_time",
lastCollectedTime ?? DateTime.UtcNow.AddMinutes(-10));
command.Parameters.Add(new SqlParameter("@cutoff_time", SqlDbType.DateTime2) { Value = lastCollectedTime ?? DateTime.UtcNow.AddMinutes(-10) });

try
{
Expand Down
Loading
Loading