Skip to content

Azure SQL Database shows its own CPU count, labels its memory a limit in tabs, cards and get_memory_stats, and says vCores in CPU right-sizing - #4882

Merged
erikdarlingdata merged 25 commits into
devfrom
fix/azure-sql-database-host-hardware-leftovers
Oct 1, 2026
Merged

erikdarlingdata merged 25 commits into
devfrom
fix/azure-sql-database-host-hardware-leftovers

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

This builds on #4879, which is merged, so the diff against dev is this PR only.

What was wrong

On an Azure SQL Database (engine edition 5), #4879 treated the cpu_count that sys.dm_os_sys_info reports as the host's CPU count. It hid Logical CPUs, and its comments said so. Measurements on real databases show that cpu_count and max_workers_count belong to the database. Only four values are the host's:

  • physical_memory_mb (about 912 GB)
  • socket_count
  • cores_per_socket (32 or 64)
  • hyperthread_ratio (64 or 128)

These figures are the database's own:

  • cpu_count is the number of online schedulers the database can see. It can be higher than its vCores. A 1-vCore General Purpose database reads 2, and a 2-vCore Hyperscale database reads 2.
  • max_workers_count is the database's own worker ceiling. The same two databases read 479 and 558.
  • The committed target in memory_stats is the database's own memory limit. The same two databases read 1,838 MB and 4,476 MB.

CPU percent on an Azure SQL Database is measured against the vCores that the service objective gives the database. So the CPU math uses the vCores and never falls back to cpu_count.

What changes on an Azure SQL Database

Surface Field Change
Server Properties (Darling web), Server Inventory (Lite and Darling Viewer), get_server_properties (Lite and Darling) Logical CPUs (cpu_count) Shown again, as the database's own scheduler count. #4879 hid it.
The same surfaces Memory, sockets, cores per socket, hyperthread ratio Still blank or null, because the host owns them. The note names these four. get_server_properties nulls four keys, not five.
Memory tab (Lite, Darling Viewer, Darling web) and the ready-made memory dashboard total_physical_memory_mb Labeled "Memory limit", not "Physical Memory". The value is the database's committed target.
The same surfaces available_physical_memory_mb Labeled "Available under limit", not "Available Physical". The value is the committed target minus what is committed.
get_memory_stats (Lite and Darling) engine_edition New key, so a client can tell which label applies.
get_memory_stats (Lite and Darling) memory_note New key, on an Azure SQL Database only, and absent on other editions. It names total_physical_memory_mb as the database's memory limit (its committed target), not the host's memory. It names available_physical_memory_mb as what is left under that limit. It says that a utilization near 100% is normal once the database has grown to its limit. Both apps add it with ServerHardwareScope.WithMemoryNote. The full text follows the table.
FinOps utilization card (Lite and Darling Viewer) CPU count unit Reads "vCores" beside the count, not "CPUs".
FinOps CPU right-sizing recommendation (Lite and Darling Viewer) Finding and detail text Names the count "vCores" where it said "cores", as the utilization card does. For example: "CPU over-provisioned (32 vCores, P95 = 12.0%)" and "Consider reducing to ~5 vCores." Other editions keep "cores". Lite builds the text in LocalDataService. The Darling Viewer's rule moved into ViewerDataService.BuildCpuRightSizingRecommendation, so a test can run it without a database. Both use ServerHardwareScope.CpuCoreNoun.
FinOps utilization card (Lite and Darling Viewer) Worker Threads in use Reads "n/a / max" where the count was not collected, not "0 / max". The maximum is shown as stored.
The memory stats collector, the utilization, trend and Server Inventory reads (Lite and Darling), and the provisioning verdict current_workers_count Stays NULL where the collector cannot read it, and is no longer turned into 0. The verdict treats it as unknown, never as zero in use and never as saturation.
FinOps health score (Lite and Darling Viewer): the score card and the per-day scores CPU term Left out when the window holds no CPU sample, on every edition. Before, the missing sample scored as 0% CPU, which is a full 100. Memory and storage keep their weights over their own total. The card tooltip says why.
SERVER_HARDWARE analysis fact (Lite and Darling fact collectors) cpu_count, vcore_count and the host's four values Carries the vCores as cpu_count and vcore_count, and none of the host's four values. A DTU objective or an elastic pool has no vCores, so it has no fact. The fact no longer falls back to the stored cpu_count. get_analysis_facts returns it.
MAXDOP advice (CONFIG_MAXDOP, THREADPOOL_PARALLEL, THREADPOOL_MIXED, the MAXDOP sentence on the parallelism waits) and audit_config (Lite and Darling) Recommended MAXDOP A guard. A user sees nothing from it on an Azure SQL Database today, because the CONFIG_MAXDOP and CONFIG_CTFP facts that these read do not exist there. The advice prints its static text, and audit_config adds no MAXDOP row. If those facts ever exist for such a database, the MAXDOP follows the vCores. See the MAXDOP section below.
Lock-pages-in-memory advisory (Lite and Darling fact collectors) RAM gate Skipped, because the gate reads physical_memory_mb, which is the host's.
Plan viewer Server Context card (Lite drill-down and local plan metadata, Darling server metadata) Hardware row Shows "N vCores", or "n/a" for a DTU objective or an elastic pool. Before, it showed the stored CPU count and the host's RAM. Memory is not shown, because the stored figure is the host's.
Top-queries and top-procedures tools (Lite and Darling) Note on a CPU ratio that is not applicable Says that the stored CPU count is the scheduler count the database can see, not the CPU it is given.

The memory_note reads as follows. Lite builds the get_memory_stats payload in McpMemoryTools.MemoryStatsPayload, and Darling builds it in DarlingMcpDataTools.

On an Azure SQL Database total_physical_memory_mb is the database's memory limit (its committed target), not the host's memory. available_physical_memory_mb is what is left under that limit. memory_utilization_pct is the share of that limit in use: a value near 100% is normal once the database has grown to its limit, and is not memory pressure by itself.

This branch merged dev, which has #4888. get_memory_stats on an Azure SQL Database keeps both of its notes. memory_note is from this PR. system_memory_state_note is from #4888. It explains why system_memory_state is null on edition 5.

engine_edition is null when the edition is unknown. memory_note exists on edition 5 only, and it is the last key. The web reads the same payload, so its "Memory limit" captions still follow engine_edition.

get_memory_stats reads one edition and uses it for every field that depends on the edition. Darling reads it once from the registry, which is the source that every other Darling MCP not_collected answer reads through DarlingEngineCapability. So the tool cannot disagree with its own gates.

The Darling memory read carries no edition. Its statement reads no server_properties, and MemoryStatsRow has no EngineEdition. Lite reads its one source once, GetSqlEngineEditionAsync, which is the newest collected server_properties row. The Lite and Viewer latest-memory reads carry no edition either, so no MemoryStatsRow has an EngineEdition member. Memory > Overview reads one edition for every line in the panel: the tab's own.

Code comments and test names that called cpu_count the host's now say that it is the database's scheduler count. One more comment is corrected. A comment in the Lite test data seeder said that a DTU objective has no CPU count of its own. It now says that its cpu_count is its own scheduler count, and that its objective names no vCore count.

Surfaces that show Logical CPUs again

  • Darling web, Server Properties: the Logical CPUs tile.
  • Lite, Server Inventory: the Logical CPUs cell.
  • Darling Viewer, Server Inventory: the Logical CPUs cell.
  • get_server_properties in Lite and in Darling: the cpu_count key.

The MAXDOP guard and the server-config target on an Azure SQL Database

The MAXDOP work in this PR is a guard, and it changes nothing that a user sees on an Azure SQL Database today. The server_config collector does not run there, as the first item below shows. So the CONFIG_MAXDOP and CONFIG_CTFP facts, and the maxdop and ctfp server-config targets, never exist for such a database. The MAXDOP advice and audit_config read those facts. The advice prints its static text when they are missing, and audit_config adds no MAXDOP row. The only vCores output that a user can reach on an Azure SQL Database is the SERVER_HARDWARE fact itself, through get_analysis_facts.

If a later change collects the CONFIG facts for such a database, the guard applies. FactRemediation.MaxdopBasisFrom reads the vCores, and the recommended MAXDOP is the smaller of the vCores and 8. Without vCores the general cap of 8 stands. The text says "vCores" and never "cores per socket", and audit_config says "this database's vCores". The CONFIG_MAXDOP advice names ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = n in place of the Apply button's sp_configure. The tests feed those facts in directly.

FactRemediation.ExtractServerConfigTargets builds a Maxdop target from the cores_per_socket of a server_config drill-down row. It has no edition check. On an Azure SQL Database that target is never built, and nothing in Lite or Darling can run it. No code changed for the target, because three guards that already exist keep it right.

  1. The server_config collector does not run on an Azure SQL Database. ServerConfigCollector.AppliesTo returns false there, because sys.configurations is not exposed. Both apps ask CollectorCatalog.AppliesTo before they collect. So no server_config rows exist, no CONFIG_MAXDOP fact exists, and no finding roots on it. CollectorGateSurfacePinTests and ConfigCollectorsDefinitionTests pin the gate.
  2. The extractor reads a server_config array from the finding's drill-down. Lite and Darling call BuildServerConfigAction, but their drill-down collectors write no such array. Only the retired Dashboard's drill-down collector writes it. That collector reads the host's cores_per_socket from its own tables, and the Dashboard does not support Azure SQL Database. So no Maxdop target and no recommended value exist in either app, and no card carries one.
  3. Neither app has an Apply for a server-config target. Lite has no in-app executor, and the Darling viewer is advise-only. The only code that runs sp_configure for such a target is the retired Dashboard's ServerConfigHandler. The shared RenderCopyPasteCommand prints sp_configure text for a persisted Maxdop target, but data from edition 5 never produces one.

Microsoft documents the database-scoped statement as ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = value, and its Applies to line includes Azure SQL Database. The MAXDOP page for Azure SQL Database shows the same statement. It says that the setting takes effect immediately and that the default for a new single or elastic pool database is 8.

Tests

Full suites on head 15e1b1f7e: Lite: 6,648 tests, 0 failed, 0 skipped. Darling: 19,245 tests, 0 failed, 1,210 skipped, 1 not run. The live PostgreSQL tests skip because no connection is set.

Memory > Overview reads one edition for every line in the panel: the tab's own. In Lite that is _engineEdition, and in the Darling Viewer it is the registry's edition, _server.EngineEdition. So the captions, the page-file figures and the memory state cannot disagree. The latest-memory reads carry no edition. The server_properties subselects and the row members are gone.

The Darling store has no v_server_properties view (Lite has one). A read of that name fails on PostgreSQL with 42P01, and only the live tests see that failure. The new test DarlingReadsUseOnlyStoreViewsTests guards every Darling read. It reads the source of four projects with the comments removed: PerformanceMonitor.Darling.Service, PerformanceMonitor.Darling.Viewer, PerformanceMonitor.Darling.Analysis and PerformanceMonitor.Darling.Storage. It fails when a name after FROM or JOIN that starts with v_ is not a view that the store creates. It needs no database.

These tests are new in the last commits:

  • GetMemoryStats_OnAzureSqlDatabase_CarriesAMemoryNote_ThatCallsTheTotalTheDatabasesLimit_AndNearFullNormal (Lite and Darling): on edition 5 the payload carries memory_note, equal to the shared text. The figures and key names are as before, and the note is the last key.
  • GetMemoryStats_ReadFromTheStore_CarriesTheNoteOnlyWhenTheStoredEngineEditionIsAnAzureSqlDatabase (Lite): a stored edition 5 row gets the note, and a stored edition 3 row does not.
  • GetMemoryStatsTool_BuildsItsPayloadThroughTheSharedNote (Lite and Darling): a source pin. The tool builds its payload through ServerHardwareScope.WithMemoryNote.
  • CpuCoreNoun_IsVcoresOnAzureSqlDatabase_AndCoresEverywhereElse (Lite and Darling): the shared word is "vCores" on edition 5 and "cores" on every other edition.
  • AzureSqlDatabase_MemoryAndVmRightSizingAdviseNothing_BecauseItsMemoryComesWithItsServiceObjective (Lite): now also pins "32 vCores" in the CPU recommendation's finding and detail, and no " cores".
  • WithCpuSamplesOffAzureSqlDatabase_CpuMemoryAndVmRightSizingStillAdvise (Lite, editions 3 and 8): now also pins "32 cores" in the finding and detail, and no "vCores".
  • CpuRightSizingRecommendation_OnAzureSqlDatabase_NamesTheVcores_InTheFindingAndTheDetail (Darling): runs ViewerDataService.BuildCpuRightSizingRecommendation without a database, and pins "vCores" in the finding and detail.
  • CpuRightSizingRecommendation_OffAzureSqlDatabase_KeepsTheWordCores (Darling, editions 3, 8 and none): pins "cores", and a savings estimate where there is a budget.
  • CpuRightSizingRecommendation_AdvisesNothing_WithoutACpuSample_OnABusyServer_OrWithFourOrFewerCpus (Darling): pins the cases where the rule stays quiet.
  • GetMemoryStats_ReadsTheEditionOnce_FromTheRegistry_AndEverythingEditionDependentFollowsIt (Darling): a source pin. The tool makes one EngineEditionAsync call, and it is the registry reader. The payload's engine_edition, memory_note and memory-state fields use that value.
  • TheLatestMemoryReads_CarryNoEngineEdition_SoNoSecondSourceCanDisagreeWithTheRegistry (Darling): neither the service's memory statement nor the Viewer's memory statement reads server_properties or engine_edition. Neither MemoryStatsRow has an EngineEdition member.
  • GetMemoryStats_EngineEdition_MemoryNote_AndTheStateNote_AllFollowTheOneEditionTheToolReads (Darling, editions 5, 3 and none): engine_edition, memory_note, system_memory_state and system_memory_state_note flip together with the one edition. Only edition 5 gets the note and a null state.
  • GetMemoryStats_OffAzureSqlDatabase_KeepsTheStoredState_AndCarriesNoNotes (Lite and Darling, editions 3, 8 and none): the stored state stays, memory_note is absent, and system_memory_state_note is null. An unknown edition shows engine_edition as null.
  • GetMemoryStats_ReadsTheEditionOnce_FromTheEngineCapabilitySource_AndEverythingEditionDependentFollowsIt (Lite): the same source pin for McpEngineCapability.EngineEditionAsync.
  • GetMemoryStats_EngineEdition_MemoryNote_AndTheStateNote_AllFollowTheOneEditionTheToolReads (Lite, editions 5, 3, 8 and none): engine_edition, memory_note, system_memory_state and system_memory_state_note flip together with the one edition the tool reads. Only edition 5 gets the note and a null state.
  • TheLatestMemoryRead_CarriesNoEngineEdition_SoNoSecondSourceCanDisagreeWithTheTabsOrTheToolsOwn (Lite): a source pin. The latest-memory read takes FROM v_memory_stats and nothing from server_properties. It has no engine_edition column, and MemoryStatsRow has no EngineEdition property.
  • TheMemoryOverview_ReadsTheTabsOwnEdition_ForItsCaptionsAndForItsOtherLines (Lite): a source pin on the Memory > Overview method. The two captions take _engineEdition. The page-file figures and the memory state take _isAzureSqlDatabase, which is that field compared with edition 5. The method reads no edition off the memory row.
  • TheMemoryOverview_ReadsTheRegistrysEdition_ForItsCaptionsAndForItsOtherLines (Darling): a source pin on the Viewer's Memory > Overview method. All five lines that read an edition read _server.EngineEdition. They are the two captions, the two page-file figures and the memory state.
  • CpuAndVmRightSizing_StandDownWithNoCpuSample (Darling, repointed): the CPU half reads the guard in BuildCpuRightSizingRecommendation (util == null || !util.HasCpuSample || util.P95CpuPct >= 30) and the call to it in GetRecommendationsAsync. The VM half still reads GetRecommendationsAsync.

Each line below names a change that was made to the product code, then the tests that went red with it. The code was restored after each run, and git status was clean after each restore. The server-config target item needs no proof, because no code changed. The shared note wording and CpuCoreNoun live in ServerHardwareScope, so their proofs ran in Lite.

  • Change made: SERVER_HARDWARE fact carries the host cores_per_socket on edition 5. Red in Lite: MaxdopBasis_OnAzureSqlDatabase_FollowsTheVcores_NeverTheHostsCoresPerSocket, MaxdopAdvice_OnAzureSqlDatabase_NamesTheVcores_NeverCoresPerSocket_AndTheDatabaseScopedStatement, ServerHardwareFact_OnAzureSqlDatabase_CarriesTheVcoresAndHadrOnly_NotTheHostsTopologyOrMemory, Collector_OnAzureSqlDatabaseWithVcores_EmitsTheVcoresAndNoHostTopologyOrMemory. Red in Darling: ServerHardwareFact_OnAzureSqlDatabase_CarriesTheVcoresAndHadrOnly_NotTheHostsTopologyOrMemory, MaxdopBasis_OnAzureSqlDatabase_FollowsTheVcores_NeverTheHostsCoresPerSocket, MaxdopAdvice_OnAzureSqlDatabase_NamesTheVcores_NeverCoresPerSocket_AndTheDatabaseScopedStatement.
  • Change made: SERVER_HARDWARE fact carries the host physical memory on edition 5. Red in Lite: ServerHardwareFact_OnAzureSqlDatabase_CarriesTheVcoresAndHadrOnly_NotTheHostsTopologyOrMemory, Collector_OnAzureSqlDatabaseWithVcores_EmitsTheVcoresAndNoHostTopologyOrMemory. Red in Darling: ServerHardwareFact_OnAzureSqlDatabase_CarriesTheVcoresAndHadrOnly_NotTheHostsTopologyOrMemory.
  • Change made: Lite fact collector reads the stored cpu_count in place of the vCores on edition 5. Red in Lite: Collector_OnAzureSqlDatabaseWithVcores_EmitsTheVcoresAndNoHostTopologyOrMemory, Collector_OnAzureSqlDatabaseWithNoVcores_EmitsNoServerHardwareFact_AndNothingFromTheHost.
  • Change made: Darling fact collector reads the stored cpu_count in place of the vCores on edition 5. Red in Darling: FactCollectorAndServerMetadataReader_AskTheSharedRule_ForTheEditionsHardware.
  • Change made: Database MAXDOP advice names sp_configure again. Red in Lite: MaxdopAdvice_OnAzureSqlDatabase_NamesTheVcores_NeverCoresPerSocket_AndTheDatabaseScopedStatement. Red in Darling: MaxdopAdvice_OnAzureSqlDatabase_NamesTheVcores_NeverCoresPerSocket_AndTheDatabaseScopedStatement.
  • Change made: Card shows the stored cpu_count in place of the vCores. Red in Lite: PlanMetadata_DrillDownRead_CarriesTheDatabasesOwnFigures, ServerContextCard_OnAzureSqlDatabase_ShowsTheVcores_NeverTheSchedulerCountOrTheHostsRam_AndNaWhereThereAreNone, PlanMetadata_LocalDataServiceRead_CarriesTheDatabasesOwnFigures. Red in Darling: ServerContextCard_OnAzureSqlDatabase_ShowsTheVcores_NeverTheSchedulerCountOrTheHostsRam_AndNaWhereThereAreNone.
  • Change made: Card shows 0 vCores in place of n/a for a DTU database or elastic pool. Red in Lite: ServerContextCard_OnAzureSqlDatabase_ShowsTheVcores_NeverTheSchedulerCountOrTheHostsRam_AndNaWhereThereAreNone, PlanMetadata_DrillDownRead_CarriesTheDatabasesOwnFigures, PlanMetadata_LocalDataServiceRead_CarriesTheDatabasesOwnFigures. Red in Darling: ServerContextCard_OnAzureSqlDatabase_ShowsTheVcores_NeverTheSchedulerCountOrTheHostsRam_AndNaWhereThereAreNone.
  • Change made: Card shows the host RAM beside the vCores. Red in Lite: ServerContextCard_OnAzureSqlDatabase_ShowsTheVcores_NeverTheSchedulerCountOrTheHostsRam_AndNaWhereThereAreNone, PlanMetadata_DrillDownRead_CarriesTheDatabasesOwnFigures, PlanMetadata_LocalDataServiceRead_CarriesTheDatabasesOwnFigures. Red in Darling: ServerContextCard_OnAzureSqlDatabase_ShowsTheVcores_NeverTheSchedulerCountOrTheHostsRam_AndNaWhereThereAreNone.
  • Change made: Lite plan metadata read drops the vCores. Red in Lite: FactCollectorAndPlanReaders_AskTheSharedRule_ForTheEditionsHardware, PlanMetadata_LocalDataServiceRead_CarriesTheDatabasesOwnFigures.
  • Change made: Lite plan metadata read passes the host RAM through. Red in Lite: FactCollectorAndPlanReaders_AskTheSharedRule_ForTheEditionsHardware, PlanMetadata_LocalDataServiceRead_CarriesTheDatabasesOwnFigures.
  • Change made: Lite drill-down metadata read drops the vCores. Red in Lite: FactCollectorAndPlanReaders_AskTheSharedRule_ForTheEditionsHardware, PlanMetadata_DrillDownRead_CarriesTheDatabasesOwnFigures.
  • Change made: Lite drill-down metadata read passes the host RAM through. Red in Lite: FactCollectorAndPlanReaders_AskTheSharedRule_ForTheEditionsHardware, PlanMetadata_DrillDownRead_CarriesTheDatabasesOwnFigures.
  • Change made: Darling server metadata read drops the vCores. Red in Darling: FactCollectorAndServerMetadataReader_AskTheSharedRule_ForTheEditionsHardware.
  • Change made: Darling server metadata read passes the host RAM through. Red in Darling: FactCollectorAndServerMetadataReader_AskTheSharedRule_ForTheEditionsHardware.
  • Change made: Worker Threads text reads a NULL in-use count as 0. Red in Lite: WorkerThreadsText_ReadsNotApplicableForAnInUseCountThatWasNotCollected_AndNeverZero, UtilizationRead_KeepsANullInUseCountNull_AndTheCeilingAsStored. Red in Darling: WorkerThreadsText_ReadsNotApplicableForAnInUseCountThatWasNotCollected_AndNeverZero.
  • Change made: Verdict reads an unknown in-use count as saturated. Red in Lite: Verdict_NeverReadsAnUnknownInUseWorkerCountAsSaturation, FleetRead_KeepsANullInUseCountNull_AndNeverCallsAServerSaturated, TrendRead_KeepsANullInUseCountNull_AndNeverCallsADaySaturated, UtilizationRead_KeepsANullInUseCountNull_AndTheCeilingAsStored. Red in Darling: Verdict_NeverReadsAnUnknownInUseWorkerCountAsSaturation.
  • Change made: Under-provisioned reason reads an unknown in-use count as saturated. Red in Lite: Verdict_NeverReadsAnUnknownInUseWorkerCountAsSaturation. Red in Darling: Verdict_NeverReadsAnUnknownInUseWorkerCountAsSaturation.
  • Change made: Lite utilization read turns a NULL in-use count into 0. Red in Lite: WorkerReads_KeepANullInUseCountNull_InAllThreePlaces, UtilizationRead_KeepsANullInUseCountNull_AndTheCeilingAsStored.
  • Change made: Lite utilization trend read turns a NULL in-use count into 0. Red in Lite: WorkerReads_KeepANullInUseCountNull_InAllThreePlaces.
  • Change made: Lite inventory read turns a NULL in-use count into 0. Red in Lite: WorkerReads_KeepANullInUseCountNull_InAllThreePlaces.
  • Change made: Darling utilization read turns a NULL in-use count into 0. Red in Darling: WorkerReads_KeepANullInUseCountNull_InAllThreePlaces.
  • Change made: Darling utilization trend read turns a NULL in-use count into 0. Red in Darling: WorkerReads_KeepANullInUseCountNull_InAllThreePlaces.
  • Change made: Darling inventory read turns a NULL in-use count into 0. Red in Darling: WorkerReads_KeepANullInUseCountNull_InAllThreePlaces.
  • Change made: Memory stats collector stores a NULL in-use count as 0. Red in Lite: ReadAsync_KeepsANullInUseWorkerCountNull_NotZero.
  • Change made: Lite Worker Threads card prints 0 for an unknown in-use count. Red in Lite: FinOpsTab_AsksTheSharedRules_ForTheCpuUnit_TheWorkerThreadsCard_TheHealthTooltip_AndTheInventoryCpuTerm.
  • Change made: Viewer Worker Threads card prints 0 for an unknown in-use count. Red in Darling: FinOpsTab_AsksTheSharedRules_ForTheCpuUnit_TheWorkerThreadsCard_TheHealthTooltip_AndTheInventoryCpuTerm.
  • Change made: Memory tab first figure keeps "Physical Memory" on edition 5. Red in Lite: MemoryTabLabels_NameTheDatabasesLimitOnAzureSqlDatabase_AndPhysicalMemoryEverywhereElse. Red in Darling: MemoryTabLabels_NameTheDatabasesLimitOnAzureSqlDatabase_AndPhysicalMemoryEverywhereElse.
  • Change made: Memory tab second figure keeps "Available Physical" on edition 5. Red in Lite: MemoryTabLabels_NameTheDatabasesLimitOnAzureSqlDatabase_AndPhysicalMemoryEverywhereElse. Red in Darling: MemoryTabLabels_NameTheDatabasesLimitOnAzureSqlDatabase_AndPhysicalMemoryEverywhereElse.
  • Change made: Utilization caption keeps "Physical:" on edition 5. Red in Lite: MemoryTabLabels_NameTheDatabasesLimitOnAzureSqlDatabase_AndPhysicalMemoryEverywhereElse, PhysicalMemoryCaption_NamesTheDatabasesLimit_OnAzureSqlDatabase_AndPhysicalMemoryEverywhereElse. Red in Darling: MemoryTabLabels_NameTheDatabasesLimitOnAzureSqlDatabase_AndPhysicalMemoryEverywhereElse, PhysicalMemoryCaption_NamesTheDatabasesLimit_OnAzureSqlDatabase_AndPhysicalMemoryEverywhereElse.
  • Change made: FinOps CPU unit says CPUs on edition 5. Red in Lite: CpuCountUnit_IsVcoresOnAzureSqlDatabase_AndCpusEverywhereElse. Red in Darling: CpuCountUnit_IsVcoresOnAzureSqlDatabase_AndCpusEverywhereElse.
  • Change made: Lite Memory tab no longer asks the shared rule for its first label. Red in Lite: MemoryTab_AsksTheSharedRule_ForTheNamesOfItsFirstTwoFigures.
  • Change made: Viewer Memory tab no longer asks the shared rule for its first label. Red in Darling: MemoryTab_AsksTheSharedRule_ForTheNamesOfItsFirstTwoFigures_InTheViewer_TheMcpPayload_AndTheWebTiles.
  • Change made: Lite FinOps card no longer asks the shared rule for the CPU unit. Red in Lite: FinOpsTab_AsksTheSharedRules_ForTheCpuUnit_TheWorkerThreadsCard_TheHealthTooltip_AndTheInventoryCpuTerm.
  • Change made: Viewer FinOps card no longer asks the shared rule for the CPU unit. Red in Darling: FinOpsTab_AsksTheSharedRules_ForTheCpuUnit_TheWorkerThreadsCard_TheHealthTooltip_AndTheInventoryCpuTerm.
  • Change made: Lite get_memory_stats drops engine_edition. Red in Lite: MemoryTab_AsksTheSharedRule_ForTheNamesOfItsFirstTwoFigures.
  • Change made: Darling get_memory_stats drops engine_edition. Red in Darling: MemoryTab_AsksTheSharedRule_ForTheNamesOfItsFirstTwoFigures_InTheViewer_TheMcpPayload_AndTheWebTiles.
  • Change made: Web Memory tab keeps its "Physical" tile on edition 5. Red in Darling: MemoryTab_AsksTheSharedRule_ForTheNamesOfItsFirstTwoFigures_InTheViewer_TheMcpPayload_AndTheWebTiles.
  • Change made: Ready-made dashboard keeps its "Physical" caption on edition 5. Red in Darling: MemoryTab_AsksTheSharedRule_ForTheNamesOfItsFirstTwoFigures_InTheViewer_TheMcpPayload_AndTheWebTiles.
  • Change made: Lite health score scores a window with no CPU sample. Red in Lite: HealthScore_WithNoCpuSample_LeavesTheCpuTermOut_AndDoesNotScoreTheZeroItReadsAs.
  • Change made: Viewer health score scores a window with no CPU sample. Red in Darling: HealthScore_WithNoCpuSample_LeavesTheCpuTermOut_AndDoesNotScoreTheZeroItReadsAs.
  • Change made: Lite Overall counts a missing CPU score as a full 100. Red in Lite: Overall_WithNoCpuScore_WeighsMemoryAndStorageOverTheirOwnSixtyPercent, HealthScore_WithNoCpuSample_LeavesTheCpuTermOut_AndDoesNotScoreTheZeroItReadsAs.
  • Change made: Viewer Overall counts a missing CPU score as a full 100. Red in Darling: HealthScore_WithNoCpuSample_LeavesTheCpuTermOut_AndDoesNotScoreTheZeroItReadsAs, Overall_WithNoCpuScore_WeighsMemoryAndStorageOverTheirOwnSixtyPercent.
  • Change made: Get_server_properties nulls cpu_count on edition 5 again. Red in Lite: GetServerProperties_OnAzureSqlDatabase_ReturnsTheHostsFourAsNull_PassesItsOwnCpuCountThrough_AndReturnsTheVcores_WithANote, GetServerProperties_OnAzureSqlDatabase_WithNoVcores_StillHidesTheHost_KeepsItsCpuCount_AndReturnsANullVcoreCount, ServerPropertiesReads_OnAzureSqlDatabase_HideTheHostsFourFigures_AndShowTheDatabasesOwnCpuCount. Red in Darling: GetServerProperties_OnAzureSqlDatabase_WithNoVcores_StillHidesTheHost_KeepsItsCpuCount_AndReturnsANullVcoreCount, ServerPropertiesReads_OnAzureSqlDatabase_HideTheHostsFourFigures_AndShowTheDatabasesOwnCpuCount, GetServerProperties_OnAzureSqlDatabase_ReturnsTheHostsFourAsNull_PassesItsOwnCpuCountThrough_AndReturnsTheVcores_WithANote.
  • Change made: Web Server Properties hides Logical CPUs on edition 5 again. Red in Darling: WebServerProperties_DescriptorHidesTheHostFour_OnEdition5_KeepsLogicalCpus_AndShowsTheServiceObjectiveAndVcores.
  • Change made: Lite Server Inventory hides Logical CPUs on edition 5 again. Red in Lite: InventoryRow_OnAzureSqlDatabase_LeavesTheHostCellsBlank_ShowsItsOwnCpuCount_AndSaysWhy, InventoryRow_OnAzureSqlDatabase_DoesNotDependOnTheOrderTheLoaderAssignsInAndKeepsAReadsOwnReason, ServerPropertiesReads_OnAzureSqlDatabase_HideTheHostsFourFigures_AndShowTheDatabasesOwnCpuCount.
  • Change made: Viewer Server Inventory hides Logical CPUs on edition 5 again. Red in Darling: InventoryRow_OnAzureSqlDatabase_LeavesTheHostCellsBlank_ShowsItsOwnCpuCount_AndSaysWhy, ServerPropertiesReads_OnAzureSqlDatabase_HideTheHostsFourFigures_AndShowTheDatabasesOwnCpuCount, InventoryRow_OnAzureSqlDatabase_DoesNotDependOnTheOrderTheLoaderAssignsInAndKeepsAReadsOwnReason.
  • Change made: Hardware_note calls cpu_count something other than the database's own scheduler count. Red in Lite: GetServerProperties_OnAzureSqlDatabase_ReturnsTheHostsFourAsNull_PassesItsOwnCpuCountThrough_AndReturnsTheVcores_WithANote. Red in Darling: GetServerProperties_OnAzureSqlDatabase_ReturnsTheHostsFourAsNull_PassesItsOwnCpuCountThrough_AndReturnsTheVcores_WithANote.
  • Change made: Lite utilization read takes the host physical memory in place of memory_stats. Red in Lite: HealthScore_OnAzureSqlDatabase_CarriesTheMemoryTermScoredFromMemoryStats, UtilizationRead_TakesTheMemoryFiguresFromMemoryStats_OnEveryEdition, HealthScore_OnSqlServerAndManagedInstance_IsTheSameScoreFromTheSameMemoryStats.
  • Change made: Viewer utilization read takes the host physical memory in place of memory_stats. Red in Darling: UtilizationRead_TakesTheMemoryFiguresFromMemoryStats_NeverFromServerProperties.
  • Change made: SERVER_HARDWARE fact carries the host cores_per_socket, so audit_config recommends from it. Red in Lite: AuditConfig_OnAzureSqlDatabase_RecommendsMaxdopFromTheVcores_NotTheHostsCoresPerSocket.
  • Change made: Lite audit_config names cores-per-socket on an Azure SQL Database. Red in Lite: AuditConfig_OnAzureSqlDatabase_RecommendsMaxdopFromTheVcores_NotTheHostsCoresPerSocket.
  • Change made: Darling audit_config names cores-per-socket on an Azure SQL Database. Red in Darling: AuditConfig_TakesItsMaxdopRecommendationFromTheSharedBasis_AndNamesTheDatabasesVcores.
  • Change made: Inventory note calls Logical CPUs the host's. Red in Lite: InventoryRow_OnAzureSqlDatabase_LeavesTheHostCellsBlank_ShowsItsOwnCpuCount_AndSaysWhy. Red in Darling: InventoryRow_OnAzureSqlDatabase_LeavesTheHostCellsBlank_ShowsItsOwnCpuCount_AndSaysWhy.
  • Change made: Lite Memory > Overview takes its first label from the memory row's edition, MemoryTabTotalLabel(stats?.EngineEdition), in place of the tab's own _engineEdition. Red in Lite: TheMemoryOverview_ReadsTheTabsOwnEdition_ForItsCaptionsAndForItsOtherLines. This ran before the row's EngineEdition member was removed, so the change compiled.
  • Change made: Viewer Memory > Overview takes its second label from the memory row's edition, MemoryTabAvailableLabel(stats?.EngineEdition), in place of the registry's _server.EngineEdition. Red in Darling: TheMemoryOverview_ReadsTheRegistrysEdition_ForItsCaptionsAndForItsOtherLines. This ran before the row's EngineEdition member was removed, so the change compiled.
  • Change made: A Darling read names the v_server_properties view, which the Darling store does not have. Red in Darling: EveryViewNamedAsAFromOrJoinTarget_InTheServiceViewerAnalysisAndStorageSql_IsOneTheStoreCreates.
  • Change made: A Darling analysis read in PgFactCollector.Activity.cs names v_server_properties in place of v_query_stats. Red in Darling: EveryViewNamedAsAFromOrJoinTarget_InTheServiceViewerAnalysisAndStorageSql_IsOneTheStoreCreates. The failure named that file. The same text inside a comment stayed green, because the test removes comments first.
  • Change made: Lite get_memory_stats drops the note call, and the Lite FinOps CPU recommendation says "cores" on edition 5. Red in Lite: GetMemoryStats_OnAzureSqlDatabase_CarriesAMemoryNote_ThatCallsTheTotalTheDatabasesLimit_AndNearFullNormal, GetMemoryStatsTool_BuildsItsPayloadThroughTheSharedNote, GetMemoryStats_ReadFromTheStore_CarriesTheNoteOnlyWhenTheStoredEngineEditionIsAnAzureSqlDatabase (edition 5), AzureSqlDatabase_MemoryAndVmRightSizingAdviseNothing_BecauseItsMemoryComesWithItsServiceObjective.
  • Change made: The memory note is added on every edition (edition gate forced off). Red in Lite: GetMemoryStats_OffAzureSqlDatabase_KeepsTheStoredState_AndCarriesNoNotes (editions 3, 8 and none), GetMemoryStats_ReadFromTheStore_CarriesTheNoteOnlyWhenTheStoredEngineEditionIsAnAzureSqlDatabase (edition 3).
  • Change made: The limit wording in ServerHardwareScope changed. Red in Lite: GetMemoryStats_OnAzureSqlDatabase_CarriesAMemoryNote_ThatCallsTheTotalTheDatabasesLimit_AndNearFullNormal.
  • Change made: The near-100% clause in the note changed. Red in Lite: GetMemoryStats_OnAzureSqlDatabase_CarriesAMemoryNote_ThatCallsTheTotalTheDatabasesLimit_AndNearFullNormal.
  • Change made: CpuCoreNoun returns "cores" on edition 5. Red in Lite: CpuCoreNoun_IsVcoresOnAzureSqlDatabase_AndCoresEverywhereElse, AzureSqlDatabase_MemoryAndVmRightSizingAdviseNothing_BecauseItsMemoryComesWithItsServiceObjective.
  • Change made: CpuCoreNoun returns "vCores" on every edition. Red in Lite: CpuCoreNoun_IsVcoresOnAzureSqlDatabase_AndCoresEverywhereElse, WithCpuSamplesOffAzureSqlDatabase_CpuMemoryAndVmRightSizingStillAdvise (editions 3 and 8).
  • Change made: Darling get_memory_stats drops the note call, and the Darling Viewer CPU recommendation says "cores" on edition 5. Red in Darling: GetMemoryStats_OnAzureSqlDatabase_CarriesAMemoryNote_ThatCallsTheTotalTheDatabasesLimit_AndNearFullNormal, GetMemoryStatsTool_BuildsItsPayloadThroughTheSharedNote, CpuRightSizingRecommendation_OnAzureSqlDatabase_NamesTheVcores_InTheFindingAndTheDetail.
  • Change made: Darling's memory note is added on every edition (edition gate forced off), and the CPU stand-down changes from CpuCount <= 4 to CpuCount < 4. Red in Darling: GetMemoryStats_OffAzureSqlDatabase_KeepsTheStoredState_AndCarriesNoNotes (editions 3, 8 and none), CpuRightSizingRecommendation_AdvisesNothing_WithoutACpuSample_OnABusyServer_OrWithFourOrFewerCpus.
  • Change made: Darling get_memory_stats reads the edition a second time. Red in Darling: GetMemoryStats_ReadsTheEditionOnce_FromTheRegistry_AndEverythingEditionDependentFollowsIt (expected 1 call, got 2).
  • Change made: The server_properties subselect is added back to the Darling memory read. Red in Darling: TheLatestMemoryReads_CarryNoEngineEdition_SoNoSecondSourceCanDisagreeWithTheRegistry, MemoryUtilization_IsStillComputedFromMemoryStats_NotFromTheHostsPhysicalMemory.
  • Change made: The server_properties subselect is added back to the Lite memory read. Red in Lite: TheLatestMemoryRead_CarriesNoEngineEdition_SoNoSecondSourceCanDisagreeWithTheTabsOrTheToolsOwn.
  • Change made: Darling's state note is fixed at edition 5, as MemoryStateNoteFor(5), in place of the tool's edition. Red in Darling: GetMemoryStats_EngineEdition_MemoryNote_AndTheStateNote_AllFollowTheOneEditionTheToolReads (editions 3 and none).
  • Change made: Lite get_memory_stats reads the edition a second time. Red in Lite: GetMemoryStats_ReadsTheEditionOnce_FromTheEngineCapabilitySource_AndEverythingEditionDependentFollowsIt (expected 1 call, got 2).
  • Change made: Lite engine_edition takes the row's own edition, stats.EngineEdition. Red in Lite: GetMemoryStats_EngineEdition_MemoryNote_AndTheStateNote_AllFollowTheOneEditionTheToolReads (all four cases). This ran before the row's EngineEdition member was removed, when the test name ended in _NeverTheRowsOwn. The same change no longer compiles.
  • Change made: The Darling FinOps CPU guard drops !util.HasCpuSample. Red in Darling: CpuAndVmRightSizing_StandDownWithNoCpuSample.
  • Change made: The Darling FinOps rule no longer calls BuildCpuRightSizingRecommendation (the call is replaced with null). Red in Darling: CpuAndVmRightSizing_StandDownWithNoCpuSample.

CHANGELOG

The changelog line is written from this entry at release, so CHANGELOG.md is not edited here.

SECTION: Fixed
ENTRY:

  • Azure SQL Database CPU count, memory limit and vCores ([Azure SQL Database shows its own CPU count, labels its memory a limit in tabs, cards and get_memory_stats, and says vCores in CPU right-sizing #4882]): Logical CPUs (cpu_count) show again, as the database's own scheduler count. The surfaces are Server Properties (Darling web), Server Inventory (Lite and Darling Viewer) and get_server_properties. Memory, sockets, cores per socket and hyperthread ratio stay blank because the host owns them. get_server_properties now nulls those four keys instead of five. The Memory tabs (Lite, Darling Viewer, Darling web) and the ready-made memory dashboard call total_physical_memory_mb "Memory limit" and available_physical_memory_mb "Available under limit". get_memory_stats adds engine_edition. On an Azure SQL Database it also adds a memory_note, as its last key. The note says that the total is the database's memory limit and not the host's memory. It adds that a utilization near 100% is normal once the database has grown to its limit. The FinOps utilization card (Lite and Darling Viewer) says "vCores" beside the CPU count. It shows Worker Threads in use as "n/a" instead of 0 when the count was not collected. The FinOps CPU right-sizing recommendation (Lite and Darling Viewer) says "vCores" where it said "cores". The stored current_workers_count, its reads and the provisioning verdict keep NULL as unknown. The FinOps health score (Lite and Darling Viewer) leaves the CPU term out of a window with no CPU sample, on every edition. The SERVER_HARDWARE analysis fact, which get_analysis_facts returns, carries the vCores and none of the host's memory, sockets, cores per socket or hyperthread ratio. The lock-pages-in-memory advisory is skipped. The plan viewer Server Context card shows the vCores, or n/a for a DTU objective or an elastic pool, and no memory. The top-queries and top-procedures tools describe cpu_count as the database's scheduler count in their CPU ratio note.
    REF:
    [Azure SQL Database shows its own CPU count, labels its memory a limit in tabs, cards and get_memory_stats, and says vCores in CPU right-sizing #4882]: Azure SQL Database shows its own CPU count, labels its memory a limit in tabs, cards and get_memory_stats, and says vCores in CPU right-sizing #4882

…'s hardware

On an Azure SQL Database the collected logical CPUs, sockets, cores per socket, hyperthread ratio and physical memory are the host's, not the database's allocation. get_server_properties (Lite and Darling) now returns those five as null with vcore_count and a hardware_note, the web Server Properties list hides them and shows the service objective and vCores, and the FinOps Server Inventory and utilization card stop presenting the host's memory and cores. Every other engine edition is unchanged.
…e server properties pin expects vcore_count after the clock pair
… no longer use the host's hardware

On an Azure SQL Database sys.dm_os_sys_info describes the host: a 1-vCore serverless General Purpose database read 2 CPUs and 911.9 GB of memory. Three calculations still divided by or scored those values. Each now uses the database's own figure where one is collected (vcore_count from the service objective) and is not applicable otherwise. A DTU objective names no vCores, so its CPU count is not applicable and nothing is computed from the host. SQL Server and Managed Instance are unchanged.

- CpuAttribution has an overload that takes the engine edition and vcore_count. On edition 5 it divides by the vCores, or omits the ratio with a not-applicable note. Both top-queries and top-procedures tools, in Lite and Darling, call it.
- The FinOps utilization read resolves its CPU count through the edition in SQL, in both apps, so edition 5 never falls back to the host's count. The card shows n/a for it.
- The FinOps health score leaves its memory term out on edition 5, because that term is the buffer pool's share of the host's physical memory. CPU and storage carry the score, and a tooltip says so.
…database gets no CPU advice

The CPU count on an Azure SQL Database is now its vCore count, or none for a DTU objective, never the host's count. The test that expects the CPU rule to fire on edition 5 seeded the host's 32 CPUs with no vcore_count, which is the DTU case. It now seeds a 32-vCore objective. A new test pins that a DTU database gets no CPU advice.
…ry_stats, not the host

On an Azure SQL Database the FinOps memory figures read memory_stats, where
total_physical_memory_mb is filled from committed_target_kb. That is the database's own
memory limit (1,838 MB on a 1-vCore General Purpose database), not the host's 911.9 GB.
Only server_properties holds the host's memory.

- The Utilization card shows Physical Memory and Buffer Pool % on every edition. On an
  Azure SQL Database the caption reads "Memory limit" and the verdict sentences name the
  database's memory limit.
- The health score keeps its memory term on every edition. FinOpsHealthCalculator.Overall
  takes an int memory score again.
- The memory and VM right-sizing rules still skip an Azure SQL Database, now because its
  memory comes with its service objective and cannot be resized on its own.
- Comments and tests that called the memory_stats figure the host's are corrected. New
  tests seed memory_stats and server_properties with different values, so a read that
  swaps the two tables fails.
- A DTU-model objective and an elastic pool both have no vCore count. The wording now
  names both.
…e stop using the host's figures

On an Azure SQL Database (engine edition 5) sys.dm_os_sys_info describes the host the
database runs on, so the stored cpu_count, hyperthread_ratio, socket_count,
cores_per_socket and physical_memory_mb are the host's, and the max_workers_count copied
from the same DMV follows the host's CPUs. This finishes what the CPU attribution and
FinOps utilization change left, in Lite and Darling alike.

- SERVER_HARDWARE fact: on an Azure SQL Database it carries the database's own vCores and
  the HADR flag and nothing else (no topology, no memory), and is not emitted when the
  service objective names no vCores. The LPIM advisory, which gated on the host's memory,
  is left out there. Both fact collectors go through one shared builder.
- Plan Server Context metadata (three readers): the vCores and no RAM figure on an Azure
  SQL Database; the Hardware row shows the vCores alone, or is dropped for a DTU objective.
- Worker Threads card and the worker ceiling the provisioning verdict reads (point-in-time,
  7-day trend and Server Inventory reads): no ceiling is carried on an Azure SQL Database,
  the card reads n/a, and nothing is scored against the host-derived maximum.
- FinOps health score: a window with no CPU sample leaves the CPU term out instead of
  scoring the 0 it reads as (a full 100). Applies to every edition, in the utilization
  card and in the Server Inventory grid, with a tooltip that says so.
- Memory utilization percentage: unchanged. It divides memory_stats columns, which the
  collector fills from the database's own committed target, so it is already the
  database's figure. A characterization test pins that.

The Utilization() row helper in the two AzureSqlDatabaseHostMathTests files now carries a
provisioning status, so its rows are measured windows and keep their CPU term.
…/azure-sql-database-host-hardware-leftovers

The base branch now keeps the memory figures on an Azure SQL Database, because they come
from memory_stats and not from the host. This merge takes its side wherever the two
conflict: the health score keeps its memory term on every edition, and ServerHardwareScope
no longer has HealthScoreWithoutMemoryNote.

What stays from this branch on top of that:

- FinOpsHealthCalculator.Overall takes a nullable CPU score. A window with no CPU sample
  leaves the CPU term out, and memory and storage keep their weights over their own 60.
- Both tabs keep the health score tooltip for that case. It is now one line that also
  clears the tooltip, because the memory tooltip that used to reset it is gone.
- The two test helpers that build a health score row carry a provisioning status, so their
  rows are measured windows. The memory scope test that pinned "no tooltip on the health
  score" now pins "no tooltip keyed on the edition", because the CPU tooltip exists.
- Tests that depended on a null memory score are removed, and the no-CPU-sample health
  score tests now cover edition 5 as well.
…'s own; only memory, sockets, cores per socket and hyperthread ratio are the host's (source changes; tests not yet updated)
…r real names, and update the pins to the corrected host-figure rule

Four columns are the host's (physical memory, sockets, cores per socket, hyperthread ratio). cpu_count is the database's own scheduler count, so it shows again and no calculation treats it as the host's. Memory-tab captions, the CPU unit label, the database-scoped MAXDOP advice text and the NULL in-use worker count are covered by tests that fail if the source is swapped.
…operties table, not the v_server_properties view that the Darling store does not have

Both reads added a subselect for engine_edition from v_server_properties, which exists in Lite only. On PostgreSQL it failed with 42P01 (relation does not exist), so get_memory_stats and the Viewer Memory summary errored on every Darling server. They now read the base table, as the other Darling reads of server_properties do.

A new source-text pin reads the service and viewer code with the comments removed. It fails when any name used after FROM or JOIN that starts with v_ is not a view the Darling store creates (PgSchemaGenerator.AllPassthroughViews and PayloadResolvingViews). It needs no database.
@erikdarlingdata erikdarlingdata changed the title Azure SQL Database hardware fact and worker threads stop using the host's figures, and the health score skips a missing CPU sample Azure SQL Database shows its own CPU count and memory limit, and MAXDOP advice follows its vCores Oct 1, 2026
…s are, and the FinOps CPU right-sizing text calls the vCores vCores

get_memory_stats keeps the key names total_physical_memory_mb and available_physical_memory_mb on every edition. On an Azure SQL Database they are the database's memory limit and what is left under it, and a database that has grown to its limit reads about 100% in use, which is normal there but read as OS memory pressure. On that edition only, both apps now add a memory_note that says so, built by one shared helper in ServerHardwareScope. Other editions keep the payload key for key.

The FinOps CPU right-sizing recommendation printed the vCores of an Azure SQL Database as "cores". It now names them vCores, as the utilization card does, and other editions keep "cores". The Darling Viewer's rule moves into a pure builder so it can be tested without a database.

Also fixes the MemoryStatsRow doc comment that said the MCP payload names the memory figures as the Memory tab does, and a test seeder comment that said a DTU objective has no CPU count of its own (its cpu_count is its own scheduler count; it names no vCore count).
…y_stats keeps both notes and reads one edition

ServerHardwareScope keeps this branch's WithMemoryNote, CpuCoreNoun and host-figure helpers together with dev's MemoryStateNote, MemoryStateOrNull and MemoryStateNoteFor.

get_memory_stats in both apps now takes the engine edition once and builds its payload from the row and that value. On an Azure SQL Database the payload carries engine_edition and memory_note (this branch) and a null system_memory_state with system_memory_state_note (dev). Other editions keep the stored state and carry no notes. engine_edition is null when the edition is unknown.

Darling reads the edition from the registry (DarlingEngineCapability), the source every other Darling MCP not_collected answer reads, so the tool cannot disagree with its own gates. The server_properties subselect in the MCP memory read, and the edition on its row, are removed because nothing else read them. The Viewer's own reads stay on server_properties. Lite reads McpEngineCapability.EngineEditionAsync once and does not read the row's own edition, which only the desktop Memory tab uses.

New pins: the payload fields follow the one edition in both directions, the Darling memory read carries no edition, and each tool reads its edition once from its capability class.
The CPU rule's guard moved into ViewerDataService.BuildCpuRightSizingRecommendation, so the pin read a method body that no longer held it and failed on every run. The CPU half now reads the builder's guard (util == null || !util.HasCpuSample || util.P95CpuPct >= 30) and the call to the builder in GetRecommendationsAsync. The VM half still reads GetRecommendationsAsync. Dropping !util.HasCpuSample from the guard fails the pin, and so does replacing the call with null.
@erikdarlingdata erikdarlingdata changed the title Azure SQL Database shows its own CPU count and memory limit, and MAXDOP advice follows its vCores Azure SQL Database shows its own CPU count, labels its memory a limit in tabs, cards and get_memory_stats, and says vCores in CPU right-sizing Oct 1, 2026
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review October 1, 2026 04:12
@erikdarlingdata
erikdarlingdata marked this pull request as draft October 1, 2026 04:15
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review October 1, 2026 05:11
@erikdarlingdata
erikdarlingdata merged commit 1019c21 into dev Oct 1, 2026
19 of 20 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/azure-sql-database-host-hardware-leftovers branch October 1, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant