Skip to content

Commit d5df71c

Browse files
Merge pull request #95 from erikdarlingdata/feature/fixes-85-91-93
Fix chart zero lines, collection log server name, and overview status (#85, #91, #93)
2 parents ee58115 + f8d1b63 commit d5df71c

16 files changed

Lines changed: 111 additions & 49 deletions

Dashboard/Services/DatabaseService.NocHealth.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
using System;
1010
using System.Collections.Generic;
11+
using System.Data;
1112
using System.Threading.Tasks;
1213
using Microsoft.Data.SqlClient;
1314
using PerformanceMonitorDashboard.Helpers;
@@ -631,7 +632,7 @@ ORDER BY r.total_elapsed_time DESC
631632
{
632633
using var cmd = new SqlCommand(query, connection);
633634
cmd.CommandTimeout = 10;
634-
cmd.Parameters.AddWithValue("@thresholdMs", (long)thresholdMinutes * 60 * 1000);
635+
cmd.Parameters.Add(new SqlParameter("@thresholdMs", SqlDbType.BigInt) { Value = (long)thresholdMinutes * 60 * 1000 });
635636
using var reader = await cmd.ExecuteReaderAsync();
636637

637638
while (await reader.ReadAsync())
@@ -685,7 +686,7 @@ ORDER BY percent_of_average DESC
685686
{
686687
using var cmd = new SqlCommand(query, connection);
687688
cmd.CommandTimeout = 10;
688-
cmd.Parameters.AddWithValue("@thresholdPercent", thresholdPercent);
689+
cmd.Parameters.Add(new SqlParameter("@thresholdPercent", SqlDbType.Int) { Value = thresholdPercent });
689690
using var reader = await cmd.ExecuteReaderAsync();
690691

691692
while (await reader.ReadAsync())

Dashboard/Services/DatabaseService.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
using System;
1010
using System.Collections.Generic;
11+
using System.Data;
1112
using System.Globalization;
1213
using System.Threading;
1314
using System.Threading.Tasks;
@@ -302,10 +303,10 @@ public async Task UpdateCollectorScheduleAsync(int scheduleId, bool enabled, int
302303

303304
using var command = new SqlCommand(query, connection);
304305
command.CommandTimeout = 120;
305-
command.Parameters.AddWithValue("@schedule_id", scheduleId);
306-
command.Parameters.AddWithValue("@enabled", enabled);
307-
command.Parameters.AddWithValue("@frequency_minutes", frequencyMinutes);
308-
command.Parameters.AddWithValue("@retention_days", retentionDays);
306+
command.Parameters.Add(new SqlParameter("@schedule_id", SqlDbType.Int) { Value = scheduleId });
307+
command.Parameters.Add(new SqlParameter("@enabled", SqlDbType.Bit) { Value = enabled });
308+
command.Parameters.Add(new SqlParameter("@frequency_minutes", SqlDbType.Int) { Value = frequencyMinutes });
309+
command.Parameters.Add(new SqlParameter("@retention_days", SqlDbType.Int) { Value = retentionDays });
309310

310311
await command.ExecuteNonQueryAsync();
311312
}

Installer/Program.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
using System;
1010
using System.Collections.Generic;
11+
using System.Data;
1112
using System.IO;
1213
using System.Linq;
1314
using System.Net.Http;
@@ -1341,16 +1342,16 @@ INSERT INTO PerformanceMonitor.config.installation_history
13411342

13421343
using (var insertCmd = new SqlCommand(insertSql, connection))
13431344
{
1344-
insertCmd.Parameters.AddWithValue("@installer_version", assemblyVersion);
1345-
insertCmd.Parameters.AddWithValue("@installer_info_version", (object?)infoVersion ?? DBNull.Value);
1346-
insertCmd.Parameters.AddWithValue("@sql_server_version", sqlVersion);
1347-
insertCmd.Parameters.AddWithValue("@sql_server_edition", sqlEdition);
1348-
insertCmd.Parameters.AddWithValue("@installation_type", installationType);
1349-
insertCmd.Parameters.AddWithValue("@previous_version", (object?)previousVersion ?? DBNull.Value);
1350-
insertCmd.Parameters.AddWithValue("@installation_status", status);
1351-
insertCmd.Parameters.AddWithValue("@files_executed", filesExecuted);
1352-
insertCmd.Parameters.AddWithValue("@files_failed", filesFailed);
1353-
insertCmd.Parameters.AddWithValue("@installation_duration_ms", durationMs);
1345+
insertCmd.Parameters.Add(new SqlParameter("@installer_version", SqlDbType.NVarChar, 50) { Value = assemblyVersion });
1346+
insertCmd.Parameters.Add(new SqlParameter("@installer_info_version", SqlDbType.NVarChar, 100) { Value = (object?)infoVersion ?? DBNull.Value });
1347+
insertCmd.Parameters.Add(new SqlParameter("@sql_server_version", SqlDbType.NVarChar, 500) { Value = sqlVersion });
1348+
insertCmd.Parameters.Add(new SqlParameter("@sql_server_edition", SqlDbType.NVarChar, 128) { Value = sqlEdition });
1349+
insertCmd.Parameters.Add(new SqlParameter("@installation_type", SqlDbType.VarChar, 20) { Value = installationType });
1350+
insertCmd.Parameters.Add(new SqlParameter("@previous_version", SqlDbType.NVarChar, 50) { Value = (object?)previousVersion ?? DBNull.Value });
1351+
insertCmd.Parameters.Add(new SqlParameter("@installation_status", SqlDbType.VarChar, 20) { Value = status });
1352+
insertCmd.Parameters.Add(new SqlParameter("@files_executed", SqlDbType.Int) { Value = filesExecuted });
1353+
insertCmd.Parameters.Add(new SqlParameter("@files_failed", SqlDbType.Int) { Value = filesFailed });
1354+
insertCmd.Parameters.Add(new SqlParameter("@installation_duration_ms", SqlDbType.BigInt) { Value = durationMs });
13541355

13551356
await insertCmd.ExecuteNonQueryAsync().ConfigureAwait(false);
13561357
}

Lite/Controls/ServerTab.xaml.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,8 @@ Include the latest event timestamp so acknowledgement is only
536536
DateTime? latestEventTime = null;
537537
if (blockingCount > 0 || deadlockCount > 0)
538538
{
539-
var latestBlocking = blockedProcessTask.Result.Max(r => (DateTime?)r.CollectionTime);
540-
var latestDeadlock = deadlockTask.Result.Max(r => (DateTime?)r.CollectionTime);
539+
var latestBlocking = blockedProcessTask.Result.Max(r => (DateTime?)r.EventTime);
540+
var latestDeadlock = deadlockTask.Result.Max(r => (DateTime?)r.DeadlockTime);
541541
latestEventTime = latestBlocking > latestDeadlock ? latestBlocking : latestDeadlock;
542542
}
543543
AlertCountsChanged?.Invoke(blockingCount, deadlockCount, latestEventTime);
@@ -849,12 +849,19 @@ private void UpdateBlockingTrendChart(List<TrendPoint> data, int hoursBack, Date
849849

850850
if (data.Count == 0)
851851
{
852-
/* Show empty chart with correct time range */
852+
/* No blocking events — show a flat line at zero so the chart looks active */
853+
var zeroLine = BlockingTrendChart.Plot.Add.Scatter(
854+
new[] { rangeStart.ToOADate(), rangeEnd.ToOADate() },
855+
new[] { 0.0, 0.0 });
856+
zeroLine.LegendText = "Blocking Incidents";
857+
zeroLine.Color = ScottPlot.Color.FromHex("#E57373");
858+
zeroLine.MarkerSize = 0;
853859
BlockingTrendChart.Plot.Axes.DateTimeTicksBottom();
854860
BlockingTrendChart.Plot.Axes.SetLimitsX(rangeStart.ToOADate(), rangeEnd.ToOADate());
855-
BlockingTrendChart.Plot.Axes.SetLimitsY(0, 1);
856861
ReapplyAxisColors(BlockingTrendChart);
857862
BlockingTrendChart.Plot.YLabel("Blocking Incidents");
863+
SetChartYLimitsWithLegendPadding(BlockingTrendChart, 0, 1);
864+
ShowChartLegend(BlockingTrendChart);
858865
BlockingTrendChart.Refresh();
859866
return;
860867
}
@@ -919,12 +926,19 @@ private void UpdateDeadlockTrendChart(List<TrendPoint> data, int hoursBack, Date
919926

920927
if (data.Count == 0)
921928
{
922-
/* Show empty chart with correct time range */
929+
/* No deadlocks — show a flat line at zero so the chart looks active */
930+
var zeroLine = DeadlockTrendChart.Plot.Add.Scatter(
931+
new[] { rangeStart.ToOADate(), rangeEnd.ToOADate() },
932+
new[] { 0.0, 0.0 });
933+
zeroLine.LegendText = "Deadlocks";
934+
zeroLine.Color = ScottPlot.Color.FromHex("#FFB74D");
935+
zeroLine.MarkerSize = 0;
923936
DeadlockTrendChart.Plot.Axes.DateTimeTicksBottom();
924937
DeadlockTrendChart.Plot.Axes.SetLimitsX(rangeStart.ToOADate(), rangeEnd.ToOADate());
925-
DeadlockTrendChart.Plot.Axes.SetLimitsY(0, 1);
926938
ReapplyAxisColors(DeadlockTrendChart);
927939
DeadlockTrendChart.Plot.YLabel("Deadlocks");
940+
SetChartYLimitsWithLegendPadding(DeadlockTrendChart, 0, 1);
941+
ShowChartLegend(DeadlockTrendChart);
928942
DeadlockTrendChart.Refresh();
929943
return;
930944
}
@@ -1495,13 +1509,13 @@ private static void SetChartYLimitsWithLegendPadding(ScottPlot.WPF.WpfPlot chart
14951509
dataYMin = limits.Bottom;
14961510
dataYMax = limits.Top;
14971511
}
1498-
if (dataYMax <= dataYMin) dataYMax = dataYMin + 100;
1512+
if (dataYMax <= dataYMin) dataYMax = dataYMin + 1;
14991513

15001514
double range = dataYMax - dataYMin;
15011515
double topPadding = range * 0.05;
15021516

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

15071521
chart.Plot.Axes.SetLimitsY(yMin, yMax);

Lite/Database/DuckDbInitializer.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class DuckDbInitializer
1919
/// <summary>
2020
/// Current schema version. Increment this when schema changes require table rebuilds.
2121
/// </summary>
22-
private const int CurrentSchemaVersion = 9;
22+
private const int CurrentSchemaVersion = 10;
2323

2424
public DuckDbInitializer(string databasePath, ILogger<DuckDbInitializer>? logger = null)
2525
{
@@ -328,6 +328,21 @@ Safe to ALTER because this table uses INSERT (not appender). */
328328
/* Table doesn't exist yet — will be created with correct schema below */
329329
}
330330
}
331+
332+
if (fromVersion < 10)
333+
{
334+
/* v10: Added server_name column to collection_log so log entries
335+
can be identified by server without needing a lookup table. */
336+
_logger?.LogInformation("Running migration to v10: adding server_name column to collection_log");
337+
try
338+
{
339+
await ExecuteNonQueryAsync(connection, "ALTER TABLE collection_log ADD COLUMN IF NOT EXISTS server_name VARCHAR");
340+
}
341+
catch
342+
{
343+
/* Table doesn't exist yet — will be created with correct schema below */
344+
}
345+
}
331346
}
332347

333348
/// <summary>

Lite/Database/Schema.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
3838
CREATE TABLE IF NOT EXISTS collection_log (
3939
log_id BIGINT PRIMARY KEY,
4040
server_id INTEGER NOT NULL,
41+
server_name VARCHAR,
4142
collector_name VARCHAR NOT NULL,
4243
collection_time TIMESTAMP NOT NULL,
4344
duration_ms INTEGER,

Lite/MainWindow.xaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@
9797
<Style TargetType="Ellipse">
9898
<Setter Property="Fill" Value="{DynamicResource ForegroundMutedBrush}"/>
9999
<Style.Triggers>
100-
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
100+
<DataTrigger Binding="{Binding IsOnline}" Value="True">
101101
<Setter Property="Fill" Value="{DynamicResource SuccessBrush}"/>
102102
</DataTrigger>
103-
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
103+
<DataTrigger Binding="{Binding IsOnline}" Value="False">
104104
<Setter Property="Fill" Value="{DynamicResource ErrorBrush}"/>
105105
</DataTrigger>
106106
</Style.Triggers>

Lite/MainWindow.xaml.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ private void ServerTabControl_SelectionChanged(object sender, SelectionChangedEv
249249
private void RefreshServerList()
250250
{
251251
var servers = _serverManager.GetAllServers();
252+
foreach (var server in servers)
253+
{
254+
server.IsOnline = _serverManager.GetConnectionStatus(server.Id).IsOnline;
255+
}
252256
ServerListView.ItemsSource = servers;
253257

254258
// Update UI based on server count
@@ -883,9 +887,11 @@ private void CheckConnectionsAndNotify()
883887
try
884888
{
885889
var servers = _serverManager.GetAllServers();
890+
bool needsRefresh = false;
886891
foreach (var server in servers)
887892
{
888893
var status = _serverManager.GetConnectionStatus(server.Id);
894+
server.IsOnline = status?.IsOnline;
889895
if (status?.IsOnline == null) continue;
890896

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

913919
if (wasOnline != isOnline)
914920
{
915-
RefreshServerList();
921+
needsRefresh = true;
916922
}
917923
}
924+
else
925+
{
926+
/* First time seeing this server's status — need to refresh */
927+
needsRefresh = true;
928+
}
918929

919930
_previousConnectionStates[server.Id] = isOnline;
920931
}
932+
933+
if (needsRefresh)
934+
{
935+
RefreshServerList();
936+
}
921937
}
922938
catch (Exception ex)
923939
{

Lite/Models/ServerConnection.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ public bool UseWindowsAuth
8181
_ => "Windows"
8282
};
8383

84+
/// <summary>
85+
/// Actual connection status from the most recent connection check.
86+
/// null = not checked yet, true = online, false = offline.
87+
/// </summary>
88+
[JsonIgnore]
89+
public bool? IsOnline { get; set; }
90+
8491
/// <summary>
8592
/// Display-only property for showing status in UI.
8693
/// </summary>

Lite/Services/LocalDataService.CollectionHealth.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ public async Task<List<CollectionLogRow>> GetRecentCollectionLogAsync(int server
7979
duckdb_duration_ms,
8080
rows_collected,
8181
status,
82-
error_message
82+
error_message,
83+
server_name
8384
FROM collection_log
8485
WHERE server_id = $1
8586
AND collection_time >= $2
@@ -103,7 +104,8 @@ ORDER BY collection_time DESC
103104
DuckDbDurationMs = reader.IsDBNull(4) ? null : (int?)Convert.ToInt32(reader.GetValue(4)),
104105
RowsCollected = reader.IsDBNull(5) ? null : (int?)Convert.ToInt32(reader.GetValue(5)),
105106
Status = reader.GetString(6),
106-
ErrorMessage = reader.IsDBNull(7) ? null : reader.GetString(7)
107+
ErrorMessage = reader.IsDBNull(7) ? null : reader.GetString(7),
108+
ServerName = reader.IsDBNull(8) ? null : reader.GetString(8)
107109
});
108110
}
109111

@@ -114,6 +116,7 @@ ORDER BY collection_time DESC
114116
public class CollectionLogRow
115117
{
116118
public string CollectorName { get; set; } = "";
119+
public string? ServerName { get; set; }
117120
public DateTime CollectionTime { get; set; }
118121
public int? DurationMs { get; set; }
119122
public int? SqlDurationMs { get; set; }

0 commit comments

Comments
 (0)