Skip to content

Per-server collector schedule intervals #703

@erikdarlingdata

Description

@erikdarlingdata

Problem

Both Dashboard and Lite support monitoring multiple SQL Server instances, but collector schedule intervals are not properly per-server.

  • Dashboard: Technically already per-server (each server's PerformanceMonitor DB has its own config.collection_schedule table), but the CollectorScheduleWindow UI doesn't show which server you're editing.
  • Lite: Truly global — a single collection_schedule.json applied to all servers. ScheduleManager has zero server awareness. RemoteCollectorService.RunDueCollectorsAsync() calls GetDueCollectors() once and applies the same list to all enabled servers.

There's no way to say "collect wait stats every 1 minute on the prod server but every 10 minutes on the dev box."


Dashboard: Title Fix

Add server name to CollectorScheduleWindow title and header so it's clear which server's schedule you're editing.

Files:

  • Dashboard/CollectorScheduleWindow.xaml — update Title
  • Dashboard/CollectorScheduleWindow.xaml.cs — accept server display name in constructor
  • Dashboard/ServerTab.xaml.cs (~line 1553) — pass server name to constructor

Window title becomes: "Collector Schedules - SQL2022-Prod"


Lite: Per-Server Schedules

Data Model

New JSON schema for collection_schedule.json (version 2):

{
  "version": 2,
  "default_schedule": [
    { "name": "wait_stats", "enabled": true, "frequency_minutes": 1, "retention_days": 30, "description": "..." }
  ],
  "server_overrides": {
    "<server-guid>": {
      "collectors": [
        { "name": "wait_stats", "enabled": true, "frequency_minutes": 5, "retention_days": 30 }
      ]
    }
  }
}
  • default_schedule: baseline all new servers inherit
  • server_overrides: keyed by ServerConnection.Id (GUID), full collector list per server (not sparse diff — simpler when new collectors are added in upgrades)
  • Descriptions omitted from overrides (inherited from defaults)

New model class: Lite/Models/ServerScheduleOverride.cs

ScheduleManager Refactoring

File: Lite/Services/ScheduleManager.cs

Replace _schedules list with _defaultSchedule + _serverOverrides dictionary + _serverRunState for per-server runtime tracking.

New public API:

Method Purpose
GetSchedulesForServer(serverId) Returns override if exists, else copy of default
GetDueCollectorsForServer(serverId) Replaces global GetDueCollectors()
MarkCollectorRunForServer(serverId, collectorName, runTime) Per-server run tracking
GetDefaultSchedule() Returns the default schedule list
SetScheduleForServer(serverId, schedules) Creates/updates a server override
RemoveServerOverride(serverId) Reverts server to default
HasServerOverride(serverId) Check if server has custom schedule
ApplyPresetForServer(serverId, presetName) Apply preset to one server
ApplyPresetToDefault(presetName) Apply preset to the default schedule
GetActivePresetForServer(serverId) Detect active preset for a server
CleanupRemovedServers(activeServerIds) Remove orphaned overrides

MergeNewDefaults must now apply to both _defaultSchedule and every entry in _serverOverrides.

RemoteCollectorService Changes

File: Lite/Services/RemoteCollectorService.cs

  • RunDueCollectorsAsync() (~line 238): Move GetDueCollectors call inside each server's task → GetDueCollectorsForServer(server.Id)
  • RunCollectorAsync() (~line 399): MarkCollectorRunMarkCollectorRunForServer(server.Id, ...)
  • RunAllCollectorsForServerAsync() (~line 302): Use GetSchedulesForServer(server.Id) instead of global list

SettingsWindow Changes

File: Lite/Windows/SettingsWindow.xaml (Row 6) and .xaml.cs

Replace the DataGrid+preset section with a Server Schedule Summary Panel:

Collector Schedules
+------------------------------------------------+
| Server           | Preset      | Status        |
| SQL2022-Prod     | Aggressive  | Customized    | [Edit]
| SQL2019-Dev      | Balanced    | Default       | [Edit]
| SQL2016-Legacy   | Low-Impact  | Default       | [Edit]
+------------------------------------------------+

Default Schedule: [Balanced v]     [Edit Default...]
[Apply Default to All Servers]
  • Read-only DataGrid: server name, detected preset, "Default" vs "Customized" status
  • Edit button per row opens CollectorScheduleEditorWindow
  • Default preset selector + "Edit Default..." + "Apply Default to All"

Constructor adds ServerManager parameter. MainWindow.xaml.cs line 851 passes _serverManager.

New Window: CollectorScheduleEditorWindow

New files: Lite/Windows/CollectorScheduleEditorWindow.xaml and .xaml.cs

Modal dialog (900x600) for editing one server's schedule or the default.

+--------------------------------------------------+
| Collector Schedules - SQL2022-Prod               |
| Server: SQL2022-Prod (192.168.1.50)              |
|                                                   |
| Preset: [Balanced v]       [ ] Use default sched |
|                                                   |
| +-----------------------------------------------+|
| |[x] Collector      | Freq (min) | Ret (days)  ||
| |    wait_stats      |     1      |    30       ||
| |    query_stats     |     1      |    30       ||
| |    query_store     |     5      |    30       ||
| |    ...             |            |             ||
| +-----------------------------------------------+|
|                                                   |
| [Copy from Server... v]    [Copy from Default]    |
|                                                   |
|                              [Save]  [Cancel]     |
+--------------------------------------------------+

Key behaviors:

  • "Use default schedule" checkbox: When checked, no override exists — DataGrid is read-only/muted. Unchecking creates an override by copying the default.
  • Preset ComboBox: Only active when not using default. Same presets (Custom, Low-Impact, Balanced, Aggressive).
  • DataGrid: Same columns as current (Enabled, Collector, Frequency, Retention, Schedule display, Last Run per-server).
  • "Copy from Server...": Dropdown of other servers, copies that schedule here.
  • "Copy from Default": Resets to default schedule.
  • Save/Cancel: Explicit save (matches Lite pattern).
  • When editing the default: Title says "Default Collector Schedule", no "Use default" checkbox, no "Copy from Default" button.

Migration Path

Automatic on first load after upgrade:

  1. LoadSchedules() detects missing version field → treats as v1
  2. Copies legacy collectors list to default_schedule
  3. Sets server_overrides = {}
  4. Saves as v2, backs up old file as .v1.bak
  5. All servers inherit the default (same behavior as before until user customizes)

Implementation Phases

  1. Data layer: ServerScheduleOverride model, ScheduleManager refactoring with migration
  2. Collector dispatch: RemoteCollectorService per-server dispatch
  3. UI: CollectorScheduleEditorWindow, SettingsWindow summary panel
  4. Dashboard: CollectorScheduleWindow title fix
  5. Cleanup: Server deletion cleanup, migration testing

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions