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
7 changes: 6 additions & 1 deletion Dashboard/CollectorScheduleWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,17 @@ public partial class CollectorScheduleWindow : Window
}
};

public CollectorScheduleWindow(DatabaseService databaseService)
public CollectorScheduleWindow(DatabaseService databaseService, string? serverDisplayName = null)
{
InitializeComponent();
_databaseService = databaseService;
Loaded += CollectorScheduleWindow_Loaded;
Closing += CollectorScheduleWindow_Closing;

if (!string.IsNullOrEmpty(serverDisplayName))
{
Title = $"Collector Schedules - {serverDisplayName}";
}
}

private void CollectorScheduleWindow_Closing(object? sender, CancelEventArgs e)
Expand Down
2 changes: 1 addition & 1 deletion Dashboard/ServerTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1665,7 +1665,7 @@ private void HealthDataGrid_MouseDoubleClick(object sender, System.Windows.Input

private void EditSchedules_Click(object sender, RoutedEventArgs e)
{
var scheduleWindow = new CollectorScheduleWindow(_databaseService);
var scheduleWindow = new CollectorScheduleWindow(_databaseService, _serverConnection.DisplayName);
scheduleWindow.Owner = Window.GetWindow(this);
scheduleWindow.ShowDialog();
}
Expand Down
4 changes: 2 additions & 2 deletions Lite/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ private async void ConnectToServer(ServerConnection server)
{
if (_collectorService != null)
{
var onLoadCollectors = _scheduleManager.GetOnLoadCollectors();
var onLoadCollectors = _scheduleManager.GetOnLoadCollectorsForServer(server.Id);
foreach (var collector in onLoadCollectors)
{
try
Expand Down Expand Up @@ -848,7 +848,7 @@ private void ManageServersButton_Click(object sender, RoutedEventArgs e)

private async void SettingsButton_Click(object sender, RoutedEventArgs e)
{
var window = new SettingsWindow(_scheduleManager, _backgroundService, _mcpService, _muteRuleService) { Owner = this };
var window = new SettingsWindow(_scheduleManager, _serverManager, _backgroundService, _mcpService, _muteRuleService) { Owner = this };
window.ShowDialog();
UpdateStatusBar();

Expand Down
22 changes: 22 additions & 0 deletions Lite/Models/ServerScheduleOverride.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2026 Erik Darling, Darling Data LLC
*
* This file is part of the SQL Server Performance Monitor Lite.
*
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
*/

using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace PerformanceMonitorLite.Models;

/// <summary>
/// Per-server schedule override. Contains the full collector list for one server.
/// When present, replaces the default schedule entirely for that server.
/// </summary>
public class ServerScheduleOverride
{
[JsonPropertyName("collectors")]
public List<CollectorSchedule> Collectors { get; set; } = new();
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public partial class RemoteCollectorService
public async Task EnsureBlockedProcessXeSessionAsync(ServerConnection server, int engineEdition = 0, CancellationToken cancellationToken = default)
{
/* Skip if the blocked_process_report collector is disabled */
var schedule = _scheduleManager.GetSchedule("blocked_process_report");
var schedule = _scheduleManager.GetScheduleForServer(server.Id, "blocked_process_report");
if (schedule == null || !schedule.Enabled)
{
return;
Expand Down
2 changes: 1 addition & 1 deletion Lite/Services/RemoteCollectorService.Deadlocks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public partial class RemoteCollectorService
public async Task EnsureDeadlockXeSessionAsync(ServerConnection server, int engineEdition = 0, CancellationToken cancellationToken = default)
{
/* Skip if the deadlock collector is disabled */
var schedule = _scheduleManager.GetSchedule("deadlocks");
var schedule = _scheduleManager.GetScheduleForServer(server.Id, "deadlocks");
if (schedule == null || !schedule.Enabled)
{
return;
Expand Down
22 changes: 14 additions & 8 deletions Lite/Services/RemoteCollectorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,9 @@ Record the error message so the user can see what's wrong. */
/// </summary>
public async Task RunDueCollectorsAsync(CancellationToken cancellationToken = default)
{
var dueCollectors = _scheduleManager.GetDueCollectors();
var enabledServers = _serverManager.GetEnabledServers();

if (dueCollectors.Count == 0 || enabledServers.Count == 0)
if (enabledServers.Count == 0)
{
return;
}
Expand All @@ -258,15 +257,22 @@ public async Task RunDueCollectorsAsync(CancellationToken cancellationToken = de
onlineServers.Add(server);
}

_logger?.LogInformation("Running {CollectorCount} collectors for {OnlineCount}/{TotalCount} servers ({SkippedCount} offline, skipped)",
dueCollectors.Count, onlineServers.Count, enabledServers.Count, skippedOffline);
if (onlineServers.Count == 0)
{
return;
}

_logger?.LogInformation("Checking per-server schedules for {OnlineCount}/{TotalCount} servers ({SkippedCount} offline, skipped)",
onlineServers.Count, enabledServers.Count, skippedOffline);

/* Run servers in parallel, but collectors within each server sequentially.
DuckDB is single-writer; running all collectors in parallel causes spin-wait
contention (50%+ CPU, multi-second stalls). Sequential per-server eliminates
this while still allowing multi-server parallelism. */
this while still allowing multi-server parallelism.
Each server gets its own due-collector list from per-server schedules. */
var serverTasks = onlineServers.Select(server => Task.Run(async () =>
{
var dueCollectors = _scheduleManager.GetDueCollectorsForServer(server.Id);
foreach (var collector in dueCollectors)
{
await RunCollectorAsync(server, collector.Name, cancellationToken);
Expand Down Expand Up @@ -299,8 +305,8 @@ CHECKPOINT reorganizes/truncates the database file. */
/// </summary>
public async Task RunAllCollectorsForServerAsync(ServerConnection server, CancellationToken cancellationToken = default)
{
var enabledSchedules = _scheduleManager.GetEnabledSchedules()
.Concat(_scheduleManager.GetOnLoadCollectors())
var enabledSchedules = _scheduleManager.GetSchedulesForServer(server.Id)
.Where(s => s.Enabled)
.ToList();

/* Ensure XE sessions are set up before collecting */
Expand Down Expand Up @@ -396,7 +402,7 @@ public async Task RunCollectorAsync(ServerConnection server, string collectorNam
_ => throw new ArgumentException($"Unknown collector: {collectorName}")
};

_scheduleManager.MarkCollectorRun(collectorName, startTime);
_scheduleManager.MarkCollectorRunForServer(server.Id, collectorName, startTime);

var elapsed = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
AppLogger.Info("Collector", $" [{server.DisplayName}] {collectorName} => {rowsCollected} rows in {elapsed}ms (sql:{_lastSqlMs}ms, duck:{_lastDuckDbMs}ms)");
Expand Down
Loading
Loading