Repository navigation
Darling and Lite: a daily collector runs at a time of day you set, in every surface, and a schedule Reset clears it (#4938) - #5018
Merged
Conversation
…4938, package 1) Adds CollectorRunTime to PerformanceMonitor.Collectors: the pure rules for running a once-a-day collector at a chosen time on the monitored server's wall clock. Nothing calls it yet except tests, so no behavior changes. What it holds: - SlotUtc: the UTC instant of a server's slot on a local date. - NextDue: the next due time from now, the last run (nullable), the run-time minute, the interval and the server id. - Spread (a fixed per-server offset under 60 minutes, from CadencePhaseOffset(serverId, 3600)) and Grace (60 minutes), both constants. - AllowsRunAt: only a positive multiple of 1440 minutes. - TryParse / Format for "HH:MM". - MaxStampAhead: how far ahead a run-time stamp can sit (interval + 60 + 60 minutes), for the clock-step check, so a stamp on a 25-hour autumn day is not read as a clock that stepped back. - InvalidRunAtMessage and IntervalRefusalMessage: the two error texts, one copy for every editor. - LocalIsUtc: the conversion to pass while a server's clock is not known. CadencePhaseOffset moves from DarlingWorker to CollectorCadence (same code, public). Its values are pinned first, as 24 literal (id, period) cases including period 3600, which the existing pins did not reach, and they pass unchanged on both sides of the move. DarlingWorker keeps a one-line forwarding member, so the seed jitter, the cold-start spread, the on-connect analysis stamp and their existing tests keep one spelling. Design calls: - The rules never know a time zone. They take a local-to-UTC delegate (an app passes ServerClock.ToUtc; LocalIsUtc while the clock is unknown). - Only local-to-UTC is available, so the date of "now" is found by probing the few candidate dates around the UTC date (an offset is within 14 hours) and taking the latest slot at or before the instant. The same probe dates a last run. No UTC-to-local conversion is needed. - A day is identified by its slot, not by the calendar date of a run. A slot that spills past local midnight (a late run time plus the spread) keeps the date it was computed for, so the run after it is one day later, not two. A run before the day's slot (an on-load run in the small hours) counts for the day before, so that slot is still owed. - Each day's slot is computed from that date's local time (run time plus the spread, converted), never as the last slot plus 1440 minutes, so it stays at the same local time across a daylight-saving change. - With a last run, the slots are every interval/1440 days from that run's date, so a two-day collector keeps its two-day grid. The latest slot at or before now is due when now is inside its grace (the answer is now, and the caller adds the seed jitter). Past the grace that day is skipped and the answer is the next grid slot. A slot still ahead is returned as is. With no last run the slots are every day. After a run, pass the run's instant (or the slot it ran for) as the last run. - The grace is closed at both ends: a start at the slot, or at the slot plus 60 minutes, is inside. - A last run at the bottom of the calendar (DateTime.MinValue) is read as never run, so a sentinel cannot throw in the date arithmetic. - TryParse takes exactly HH:MM (two digits, ASCII, 00:00 to 23:59) and ignores surrounding spaces; "24:00", "2:5", "2:00" and "abc" are refused. - NextDue throws for an interval AllowsRunAt refuses or a run-time minute outside 0 to 1439; callers validate with AllowsRunAt first. Tests (Darling.Tests, which references both projects, so the daylight-saving cases use the real ServerClock.ToUtc): 74 in CollectorRunTimeTests, covering before the slot, after the grace, inside the grace, a missed day, a two-day interval, a spring-forward and a fall-back day in US Eastern, every day of a year at the same local time, a fixed offset across the UTC date, an unknown clock, the spread, the clamp bound, AllowsRunAt, parse and format round trips and refusals, and a sweep over six clocks against a brute-force check of every candidate date. The tests were run against stubs that return wrong answers first: 42 of 74 failed.
…nd resolve (#4938, package 2a) Migration 158 adds config.config_collector_schedules.run_at_minute, a nullable smallint of minutes after midnight on the server's clock, CHECK -1 to 1439. NULL falls through per column as the databases column does; -1 means no fixed time on this server and stops a fleet-wide time. The CHECK is inline on the ADD COLUMN, so the statement stays idempotent and adds no validation pass. Nothing writes the column yet. The version bump brings its obligations: StorageVersion 158, the viewer probe's sentinel with its MapProbedSchemaVersion arm and GetBoolean(133), the previous top rung's tests handed off, the version-simulating live test drops the new column, and the pinned service SELECT now ends with it. ScheduleOverride and EffectiveSchedule carry the run time, and the service's store read fills it. ResolveSchedule: the server row's value wins (-1 resolves to none), else the fleet row's, else none; a value outside the CHECK's range counts as not set. A value where the effective interval is not a whole number of days (CollectorRunTime.AllowsRunAt on the recurring interval, so an on-load collector counts as daily) resolves to none. The warning for a dropped run time is raised in StoreConfigProvider.LoadViewAsync, after the servers and the schedules are read, not in ResolveSchedule: that runs every sweep and stays pure and silent, while a load happens once per config_version bump, which is once per schedule load. It is judged per enabled server, not per row, because a per-server frequency override changes the interval (a fleet-wide time can be fine on one server and refused on another), and a collector the server's engine never runs is skipped. One warning per dropped run time names the collector, the server, which row set the time, and the shared refusal text. Also fixes the stale pg_index_bloat comment: since #3234 it estimates from pg_stats and the catalog and reads no index pages.
…and word the probe comment without the banned term
…ext run, and the web Collection Health tab shows the run time (#4938)
…, #4967) pg_upgrade makes a new cluster identifier and keeps the OIDs of the database and of the install id table. Migration 159 adds table_oid to the install id row. The service decides whether a row is this store's by the two OIDs once the row has its table OID, rebinds a changed cluster identifier without changing the id, and names which binding differed when it makes a new id. A row with no table OID follows the old rule once and has it filled in.
…b health tab and Lite's MCP health tool (#4938)
… collector and its tests (#4938)
…-run-time # Conflicts: # Darling/Darling.Tests/CaggGroupIndexDropLiveTests.cs # Darling/Darling.Tests/MuteRuleServerIdRungTests.cs # Darling/PerformanceMonitor.Darling.Storage/PgMigrations.cs # Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.cs
…o feature/4938-collector-run-time # Conflicts: # Darling/Darling.Tests/CaggGroupIndexDropLiveTests.cs # Darling/PerformanceMonitor.Darling.Storage/PgMigrations.cs # Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.cs
…V158 and V159 (#4938) The run-time column's migration is version 160, appended after the install id's two rungs, and StorageVersion is 160. The viewer probe's sentinel for the column is the last arm (ordinal 135), and the version map has a newest-first arm for it that returns 160. The install id table OID rung (V159) is no longer the top of the ladder, so its tests hand the top off the way the earlier rungs' did: the rung is registered in a dense ladder below the top, and a store that stops at its sentinel maps to it. The run-time rung tests and their live half follow the new number: the map's all-true and one-behind cases, the arm's return value, and the upgrade test that starts from V159 and rewinds the version stamp from 160. Comments that named the column's version now say V160.
…nd tab-open runs from the local log, and a file with run_at (#4938)
…t, and shows the next run (#4938)
… into mcp-web-4938
… under a fleet-wide cap (#4938) Adds a stand-in for one collector run (RunOneBodyOverride) and makes the per-server due-collector pass internal, so a test can hold a daily run open and watch the pass around it. The new cases pin that: - the server pass ends while a daily run is still going, and a 1-minute collector on the same server still runs on time; - with 17 daily runs due together, 16 start, the 17th waits for a permit and starts when one finishes, and none is dropped; - a daily collector whose previous run is still going skips that tick. This commit changes no behavior: the daily run is still inline, so these cases fail here and pass with the change that follows.
…fleet-wide cap of 16 (#4938) A collector whose effective interval is a day or more (index_object_stats, the once-a-day recapture of the on-load config collectors, the PostgreSQL daily collectors, and any collector an operator slows to a day) used to run inline in its server's sequential pass. A long run, 43 minutes across 72 databases in a field case, held back that server's 1-minute collectors for its whole length. - Each daily collector is now dispatched detached, behind the per-(server, collector) single-flight slot plan_correction already uses. A held slot means the previous run has not finished and the tick skips, with a log line that says the next run is a day away. - A fleet-wide cap of 16 bounds how many detached daily runs go at once. A run over the cap waits for a permit on the service's cancellation token, so only shutdown ends the wait, and it is logged once at Debug. After a long wait the run re-reads the server and does not run if it was removed or reconnected. - A detached daily run does not fold its duration into the sweep body's peer mark, because it finishes minutes to hours after the body that dispatched it. - Detached daily runs are tracked so a shutdown waits for them as it waits for the per-server bodies. - query_store, plan_correction and pg_wait_sampling run as before, and the at-connect run of an on-load collector stays inline.
…t-collector-run-at verb (#4938, package 3a) The Collector Schedules window gets a Run at cell per collector (a HH:MM time, Use default, and None on a server row), a conversion line, and the shared checks. The save writes the edited run time and keeps an unedited one, so the test that pinned a save clearing a stored run time is renamed and now pins the new behavior. The --set-collector-run-at verb writes the same row as the viewer for the fleet or one server. These tests describe that surface; the code lands in the next commit.
…day's run, so it is not retried on every sweep of the hour (#4938) A run that did not succeed still writes its collection_log row, and the start-up read counts every row as the last run whatever its status. The session now counts the attempt the same way: a failing collector runs once inside the hour that follows its time, and its next due time is the next day's. A collector with no run time keeps the interval rule.
…ite side of the run time comes in its own change (#4938)
… server's clock, a 60-minute spread and grace, skipped days, detached runs and Lite (#4938)
…and a note when a day was skipped (#4938) A collector with a run time carries run_at (24-hour HH:MM on the monitored server's clock) and next_run_utc (the shared rules' next due time: the run time plus the server's spread, on the server's own clock from its newest server_properties row, or UTC with none). The server row wins over the fleet row, -1 on the server row stops the fleet's, and a disabled collector shows its run_at with no next run. A collector past its stale line with the next slot still ahead carries run_time_note. Health bands stay on the shipped cadence.
A PostgreSQL TimeZone the host cannot resolve reads as UTC; the worker now logs a warning once per server and zone text naming the text and saying run times use UTC.
…into feature/4938-run-time-surfaces
…y read the table, and collector writes bind the catalog's spelling (#4938)
…into feature/4938-run-time-surfaces
…the slot, and an on-load seed waits for a successful run (#4938)
…me, not the whole schedule file (#4938) A hand edit such as "run_at": 120 threw while the schedule file was read, so Lite restored the backup or the defaults and every other edit in the file was lost. The property now has a converter: a string reads as before, and any other token reads as its own JSON text, which the run-time check refuses like "25:00" (same warning), and the collector runs on its interval.
… time is logged as a warning (#4938) MainWindow built ScheduleManager with no logger, so the warning for a run_at that is not HH:MM, or a time on an hourly collector, was never written and the run time was dropped silently. It now takes an AppLoggerAdapter like the other services built beside it.
…_collection_health both read a run older than the interval plus a day as never run (#4938) For a collector on an interval of two days or more, get_collection_health used the health row's newest run with no floor while the start-up read kept a run only from the longest daily interval plus a day back, so the two could name different next runs. ScheduleManager.LastRunFloor is the collector's own interval plus a day; the tool applies it to the health row's run and the start-up read applies it to each collector it seeds (not just to the longest on the server).
…e say it uses the fleet-wide run time (#4938) With "Use default schedule" ticked the grid shows the fleet's schedule, so the selected row's Run at cell holds the fleet's own time, and the line described that as a time the server had of its own. The line now reads the cell as "Use default" for a server on the default schedule, which takes the fleet-wide time and says so. Display only: Save is unchanged.
…ides and the run times it reset apart (#4938) With only run-time rows to reset the status read "Reset 2 per-server schedule override(s)", because the call returned one number for both tables. It now returns the schedule overrides and the run times counted apart, and the status names each in the singular or plural: "Reset 2 per-server run times", or "Reset 1 per-server schedule override and 2 per-server run times".
…n reads it whole (#4938)
…e delete what the editor never showed (#4938) When the run-time read failed, the editor held no run times and its warning was overwritten by the next status text. A server whose only override is a run-time row then opened on "Use default schedule", and a plain Save cleared its run times. No successful read now means no write: after a failed run-time read the Save sends no run-time change at all (no upsert, no delete, no clear) and is refused, saying why, when it would have to write a run time the user asked for; after a failed schedule read the Save is refused, since it replaces the scope's rows. Both warnings stay in front of every later status text until a reload of that read works.
…he user can do (#4938) The text told the user to leave Reset to Defaults out, which cannot be undone once it is armed. It now names what cannot be saved (a typed time, or the Reset that clears them) and says to reopen the window to read the run times and try again.
erikdarlingdata
marked this pull request as ready for review
October 3, 2026 17:53
This was referenced Oct 3, 2026
erikdarlingdata
added a commit
that referenced
this pull request
Oct 3, 2026
…ing reaches the service log, and provision-roles.sql lists store version 160 (#5039) Three follow-ups to #5018's run times, from its post-merge review. The docs' sentence on a run held by the fleet's daily-run cap now says it starts when a place under the cap frees, which can be after the grace hour. "Slot" is the run time plus the per-server spread on the same page, so "when a slot frees" read as waiting for the next day. When the MCP role cannot read config.config_collector_run_times (SQLSTATE 42501), get_collection_health still answers with no run times, and its one warning now goes to the tool's logger, which reaches the service log. It went to System.Diagnostics.Trace before, and the service wires no Trace listener, so the warning reached nothing. Trace stays only as the fallback when no logger is passed. The same Trace pattern elsewhere is named in the PR, not changed. provision-roles.sql's header now lists config_collector_run_times as store version 160 among the single-table grants to re-run after an upgrade. Refs #4938
12 of 14 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4938.
A daily collector can have a run time, a fixed time of day such as 02:00 on the monitored server's clock. The table that holds it,
config.config_collector_run_times(V160), the shared run-time rules, and the detached daily runs with their fleet-wide cap (#4999) are already in dev. This change moves every surface onto that table. It makes the worker and Lite start a daily collector at its slot, and it adds one rule for Reset.What changes
Every Darling surface below reads or writes
config.config_collector_run_times. None of them puts a run time on a schedule row. The viewer's schedule Save deletes a scope's rows and inserts them again with a fixed column list. A viewer that does not know such a column clears it on every Save.Use default,Noneon a server row, or aHH:MMtime). A line says what the time means in server time and in UTC, and a server row also gets a next-run line. The window reads the table next to the schedule rows. A store below V160 has no table. The read answers that with no run times, so the window still opens.SaveCollectorScheduleAsyncwrites the run-time changes (one statement each) and then the scope's schedule rows on one connection in one transaction. A schedule write that fails leaves the run time unsaved, and a run-time write that fails leaves the schedule rows as they were. A Save that changed no run time sends no run-time statement, so it still saves its schedules on a store below V160. The schedule statements are the ones a released viewer runs and never name the run time.--set-collector-run-at. The service's CLI verb sets a time (HH:MM), stops a fleet-wide time on one server (none), or clears the row (default). It works fleet-wide or with--server. It writes only the run time and keeps the enabled flag, cadence and retention on the row. The service's command executor writes the same table for the matching command.get_collection_healthshows each collector's run time and next run, and a note when a day was skipped. The web Collection Health tab shows the run time beside Every (min). Its Collectors table also has Run at, Next run and Run time columns.server_properties(SQL Server) orpg_server_config(PostgreSQL), never on a tick. A slot is computed again only when the run time, the interval or the clock changes. The detached run, its cap, its permits and its watch are Every daily collector runs detached from its server's pass, behind a fleet-wide cap sized to the store's pool (#4938) #4999's. This change only decides when a collector with a run time is handed to them.run_at(HH:MMon the monitored server's clock). The schedule editor sets it, checks it and shows the next run. The sweep starts a collector with a run time once a day, from its slot until 60 minutes after it. A missed day is skipped. At start-up Lite reads each daily collector's last run fromcollection_log. It also reads the server's clock from the newestserver_propertiesrow. A restart or a tab open then no longer re-runs a daily collector that ran today. A failed attempt of a collector that runs once a day or less often ends its period, with or without a run time. Lite'sget_collection_healthcarriesrun_at,next_run_utcandrun_time_notefor its own schedule, so the two products answer alike.Darling/README.mdhas what the run time sets, what stays fixed, and the--set-collector-run-atverb.docs/how-collection-works.mdhas a section, "When the daily collectors run". Its one sentence on detached runs gives Every daily collector runs detached from its server's pass, behind a fleet-wide cap sized to the store's pool (#4938) #4999's cap. The cap is the smaller of 16 and what the store pool leaves after the sweep and 8 kept free for reads. It is never below 1. The top-levelREADME.mdgets a line onrun_at.clearScopeRunTimes. The scope's run-time rows are deleted by scope, first, in the Save's transaction. That covers a row for a collector this build does not define and a row another writer added while the window was open. A time typed into the grid after the Reset is inserted after the clear and kept.ResetAllServerSchedulesAsyncis now ONE call. In one transaction it deletes every server's schedule rows and then every server's run times. It keeps the fleet's rows in both tables. A failure in the run-time delete takes the schedule delete back with it. The 42501 error still maps to the read-only message, and 42P01 or 42703 to the schema-skew message. It returns the rows removed from each table, counted apart, and the status line names each.ResetAllServerRunTimesAsync, the separate statement the window used to call after it, is gone.config_collector_schedulesand for reset verbs. Only the viewer's data service deletes them. The service's--enable-collectorand--disable-collectorverbs and its enable and disable commands upsert oneenabledvalue. Lite keeps its schedule in its own file, not in this store.StartupCommandTimeoutTestsnow listsReadNotificationRoutesAsync, which the service's config load already runs on the serial loop. 11 members hold 12 commands, and 9 of the 12 also run once at start-up. The prose inServiceCommandDeadlines.cssays the same._dailyRuns,TrackDailyRunandInFlightDailyRunshold every detached run, the daily ones and the three detached by name. They are now_detachedRuns,TrackDetachedRunandInFlightDetachedRuns, with the tests and comments that name them. Behavior does not change.Test plan
Each RED below was run against a live store, with the code as it stood before the Reset change.
ApplyDefaultToAllServers_RemovesEveryServersScheduleRowsAndRunTimes_AndKeepsTheFleets. RED:Expected: 5, Actual: 2. GREEN after.ApplyDefaultToAllServers_ThatFailsAfterTheScheduleDelete_LeavesTheScheduleRowsAndTheRunTimesInPlace. RED:No exception was thrown. GREEN after.AScheduleSave_ThroughTheViewersRealSql_LeavesEveryRunTimeAlone_AndApplyDefaultToAllRemovesOnlyTheServerRunTimes. RED:Expected: 2, Actual: 0. GREEN after.ASaveAfterAReset_LeavesNoRunTimeRowForTheScope_AndKeepsEveryOtherScopes. RED: server 7's rows survived. GREEN after.ResetDefaults_Clickand passes it to its one Save call. It callsResetAllServerSchedulesAsynconce and has no run-time reset of its own. That method opens one transaction and deletes the schedule rows before the run times.AScheduleReset_DeletesTheScopesRunTimes_InTheSameTransactionAsItsScheduleRows. Written with the fix, and not run against the old code.AStoreWithoutTheRunTimeTable_ReadsNoRunTimes_AndASaveSaysTheStoreIsOlder. The reset half passes before and after. The Reset's Save check was added with the fix.CollectorRunTimeCliVerbTestsandCollectorRunTimeCliVerbLiveTests.TheViewerScheduleSave_LeavesEveryRunTimeRowAlone_ForAFleetScopeAndAServerScopeandTheViewersScheduleStatements_NeverNameTheRunTime.AScheduleWriteThatFails_LeavesTheRunTimeUnsaved_BecauseTheSaveIsOneTransactionandTheWindowsSave_IsOneCallInOneTransaction_AndNeverCommitsTheRunTimesOnItsOwn.DarlingWorker.cs,DarlingMcpDataTools.cs,ViewerDataService.CollectorSchedules.csandDailyCollectorDetachTests.cs. Each took dev's (Every daily collector runs detached from its server's pass, behind a fleet-wide cap sized to the store's pool (#4938) #4999's) detached-run code and kept this branch's run-time code on top.git diff origin/dev...HEADholds only run-time work and the three renames.SweepBodyDetachPolicyTests.csis dev's unchanged, andDailyCollectorDetachTests.csis dev's apart from the renames.BothSkusToolDescriptions_StayByteIdentical: 1 of 1.CollectorRunTime*,CollectionHealth*andCollectorFrequency*: Total 112, Failed 0.3f3fbd66e. An earlier local live run on a throwaway cluster did not complete: it had 24 command timeouts under the parallel load, and then the cluster crashed (could not reserve shared memory region). Every class that timed out passed when re-run alone on a fresh database:CollectorRunTimeCliVerbLiveTests6 of 6, and the other 15 classes 57 of 57.ToRunTimeChangesand the source pin cover the Reset and Save, not a click-through.Fixes from review
What changes (Lite)
run_atincollection_schedule.jsonthat is not a JSON string, such as"run_at": 120,true, an object or an array, now costs only that collector's run time. Before, the whole file failed to load, Lite fell back to the backup or the defaults, and every other edit in the file was lost. The value now reads as its own text, is refused the same way"25:00"is (same warning, naming the collector), and the collector runs on its interval. The file is written back with the value as text. No other property this PR added has this shape."25:00", or"02:00"on an hourly collector) was never written.ScheduleManager.LastRunFloor). A run older than that reads as never run.get_collection_healthapplies it to the health row's run. The start-up read applies it to each collector it seeds, so a weekly collector on the same server no longer widens the floor of a 3-day one. For a collector on a 2-day-or-longer interval, the tool and the scheduler now name the same next run.Test plan
"run_at": 120(also1.5,true,false, an object, an array) on one collector and a 7-minute interval on another. The 7-minute interval and the other collector's retention survive, no error is logged, one warning names the collector, the collector runs on its interval, and a reload reads the same. 6 cases failed before."run_at": "02:30","25:00"andnullread as they always did.MainWindowbuilds exactly one schedule manager and passesnew AppLoggerAdapter<ScheduleManager>(). A second test sends"25:00"(daily) and"02:00"(hourly) through that same adapter and finds the warning in the app log. This second test passes before the fix too, because the adapter already existed: the first one is the RED.next_run_utc(they differed). Cases for 2, 3 and 5 slot dates back pass before and after: 2 and 3 are inside the floor, and at 5 the old grid happens to land on the same slot.ScheduleManager,CollectorSchedule,McpCollectorRunTimes, the run-time part ofRemoteCollectorService, or readMainWindow.xaml.cs: 0 failed. After the final merge, the run-time, health-tool, delta, grid and install-id classes: 152 tests, 0 failed.AlertMasterSwitchSurface,AlertReadFailureSurface,CollectorMeasurementSeam,CollectorStateContract,FleetCardCollectionStale...,LiteJobHistorySource,MuteRuleReloadRetention,RemoteCollectorServiceCancellationCensus,TsqlConventionGuard,RepoFileAdoption,DocCommentHygiene,CommentFilterAdoption), 0 failed, 1 skipped. The member-scan guard failed once on the first version of the floor helper (an expression body it read short), so the helper is a block body.3f3fbd66e.Known limit
The health read covers seven days. The floor for an interval of seven days or more (8 days or more) is wider than that, so a run between 7 days and the floor is seen by the scheduler and not by the tool. This was already true before the floor, and closing it needs a wider read for those collectors.
What changes (Darling viewer, Collector Schedules window)
Test plan (each check was written first and fails to build on the branch as it stood)
3f3fbd66e.What changes (Darling service, MCP and docs)
get_collection_healthno longer fails as a whole when the MCP role cannot read the run-time table. That happens on a store you provisioned yourself before this table existed. The tool shows no run times and logs one warning saying to re-runprovision-roles.sql, which now grants the MCP role read onconfig.config_collector_run_times. The managed store's MCP role already reads everyconfigtable.set_collector_run_at,enable_collectoranddisable_collector, from the command plane and from the CLI, stored the name as typed. The unique indexes compare names exactly, so a typedWait_Statsand the viewer'swait_statsbecame two rows for one collector.TimeZonethis host cannot resolve gets one warning. Examples arelocaltime, or a POSIX string such asEST5EDT,M3.2.0,M11.1.0. The warning is logged once per server and zone, names the zone, and says that server's run times use UTC. Before, they moved to UTC with no message.Darling/README.md,docs/how-collection-works.md);README.mdsays a run can start nearly 2 hours afterrun_at.Test plan (Darling service and MCP)
Each check below fails when its fix is taken out. The second mutation for each was written separately from the fix.
Pass_WhileYesterdaysRunStillHoldsTheSlot_DoesNotCountTodayAsRun_AndRetriesInsideTheGraceand its past-the-grace sibling.OnLoadSeed_AfterAFailedRunAtConnect_LeavesTheSlotDueInsideTheGrace.AnInsufficientPrivilegeReadAnswersNoRunTimesWithOneWarningandAMissingTableAnswersNoRunTimesAndAnythingElsePropagates(other errors still propagate). RED: not matching 42501 fails the first.ProvisionRolesSql_GrantsMcpReadOnTheCollectorRunTimes. RED: removing the grant line fails it.ResolvePlan_CollectorWrites_BindTheCatalogsSpellingNotTheTypedOne, for every verb, scoped and fleet-wide. RED: binding the typed name forset_collector_run_atfails it.AnUnresolvedZone_WarnsOncePerServer_NamingTheZone_AndReadsAsUtcand a resolvable zone (includingUTC) logging nothing.ASave_AfterAFailedRunTimeRead_ThatChangedNothing_SendsNoRunTimeDeleteOrClear. RED: clearing the run times after a failed read fails it.--no-incremental, 0 warnings and 0 errors at the head.DarlingRunTimeSchedulingTests22,DarlingCollectorRunTimeReaderTests18,DarlingCommandExecutorTests34 (8 live skipped),ProvisionRolesAclDriftTests9,DarlingPgUnresolvedZoneWarningTests2,CollectorRunTimeCliVerbTests21,CollectorRunTimeRungTests19,CollectorScheduleEditorSaveGuardTests8,CollectorScheduleResetStatusTests8,CollectorScheduleUseDefaultRunAtLineTests4,CollectionLogDrainForensicsStoreTests13,DailyCollectorDetachTests30, and the guards (TsqlConventionGuardTests,DocCommentHygieneTests,RepoFileAdoptionTests,CommandDeadlineScannerAdoptionTests,FleetIdentifierScrubTests,QueryStoreSliceTieBreakSourceTests).CollectorRunTimeScheduleTestsandCollectorRunTimeWarningLogTests. CI's Lite tests jobs run them green at3f3fbd66e.3f3fbd66e, including the full Darling.Tests run in the PostgreSQL test jobs.Not done here, and notes
CHANGELOG
No entry in this PR. The release owner adds it under [Unreleased].
Proposed entry: