Azure SQL Database CPU math uses the vCores, and the Utilization card shows its memory figures - #4879
Merged
erikdarlingdata merged 10 commits intoOct 1, 2026
Conversation
…'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.
…-no-host-hardware-in-math
…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.
…-no-host-hardware-in-math
erikdarlingdata
marked this pull request as ready for review
September 30, 2026 22:15
erikdarlingdata
marked this pull request as draft
September 30, 2026 22:22
…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.
…-no-host-hardware-in-math
erikdarlingdata
marked this pull request as ready for review
September 30, 2026 23:44
erikdarlingdata
deleted the
fix/azure-sql-database-no-host-hardware-in-math
branch
October 1, 2026 00:10
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.
Builds on #4876, which
devholds. That PR addedServerHardwareScopeand stopped showing the stored hardware on an Azure SQL Database. This PR makes the CPU math use the database's vCores instead of the stored CPU count. It also restores the memory figures that #4876 hid, because they are the database's own.What does this PR do?
On an Azure SQL Database,
sys.dm_os_sys_infodoes not give the database's own limits. A 1-vCore serverless General Purpose database read acpu_countof 2, the same as its visible online schedulers. Its service objective caps it at 1 vCore. It also read 911.9 GB of physical memory, which is the host's.#4876 stops showing those values. Two calculations still divided by
cpu_count. This PR fixes both, in Lite and in Darling.Each calculation now uses the database's own count when one is collected. That count is
vcore_count, parsed from the service objective. When none is collected, the part is marked not applicable and nothing is computed from the host. A DTU-model objective and an elastic pool have novcore_count, so their CPU count is not applicable. SQL Server (editions 1 to 4) and Managed Instance (8) behave as before.The memory figures follow a different rule. In
server_properties, onlyphysical_memory_mb, the socket count, the cores per socket and the hyperthread ratio are the host's. Itscpu_countis the database's scheduler count, which can be larger than its vCores.The
memory_statstable is the database's own. On an Azure SQL Database the collector fillsmemory_stats.total_physical_memory_mbfromcommitted_target_kb, which is the database's memory limit. A 1-vCore General Purpose database reports 1,838 MB there and a Hyperscale database reports 4,476 MB.server_propertiesholds 911.9 GB for the same databases.#4876 treated the
memory_statsfigure as the host's. This PR removes that. The FinOps Utilization card shows its Physical Memory and Buffer Pool % on an Azure SQL Database again. The health score keeps its memory term.Why
get_top_queries_by_cpuandget_top_procedures_by_cpu) multiplied the average CPU percent by the storedcpu_count. On the 1-vCore database the denominator was twice too large, so the attributed share was half of the true one.COALESCE(vcore_count, cpu_count). With novcore_countit fell back tocpu_count, which is not the database's vCore limit.memory_stats, so they describe the database. Hiding them removed real values.I traced every input of the utilization card on a 1-vCore serverless General Purpose test database.
vcore_count, elsecpu_countcpu_countfallback is not the vCore limitvcore_count, or n/amemory_stats.total_physical_memory_mbtotal_server_memory_mbminusbuffer_pool_mb) overtotal_server_memory_mbsys.dm_db_resource_stats.avg_cpu_percent. Workspace grants fromsys.dm_exec_query_resource_semaphores. Worker threads, wherecurrent_workers_countis NULL on this edition.What changes
ServerHardwareScopegetsOwnCpuCount,CpuCountTextandPhysicalMemoryCaption. One rule serves both apps. Its verdict sentences name the buffer pool share of the database's memory limit on an Azure SQL Database.CpuAttribution.Computehas a new overload that takes the engine edition, the stored CPU count andvcore_count. On edition 5 it divides by the vCores. With no vCores it returns no ratio and the notecore count not applicable: .... Both MCP tools in both apps call it. The payload keys are unchanged.CASE WHEN engine_edition = 5 THEN vcore_count ELSE COALESCE(vcore_count, cpu_count) END. The card showsn/awhen there is no count.UtilizationEfficiencyRow.ComputeHealthScore()exists in both apps and scores memory the same way on every edition.FinOpsHealthCalculator.Overalltakes an int memory score, as it did ondev.server_propertieshardware stays hidden. The web Server Properties tiles, the Server Inventory hardware cells and the hardware fields ofget_server_propertiesare not changed.AzureSqlDatabaseMemoryScopeTestsin both test projects givememory_statsandserver_propertiesdifferent values, 1,838 MB and 933,836 MB. Lite seeds both tables in DuckDB. Darling uses in-memory rows and checks its PostgreSQL reads as SQL text. A read that swaps the two tables fails in both.deva third time. The new commit, File I/O on Azure SQL Database Hyperscale shows the real data file size and no size for the log file #4878, touches other files and merged with no conflicts. The diff againstdevis 25 files.I did not change the CPU right-sizing rule in the recommendations code. It reads the same row's
CpuCount. So its rule (CpuCount > 4) can no longer fire from the storedcpu_counton a DTU-model objective or an elastic pool.That changes one test that #4877 added. It seeded an Azure SQL Database with a stored
cpu_countof 32 and novcore_count, and expected the CPU rule to fire from that count. It now seeds a 32-vCore objective, so the rule still fires from the database's own count. A twin pins that a DTU database gets no CPU advice. The test is now namedAzureSqlDatabase_MemoryAndVmRightSizingAdviseNothing_BecauseItsMemoryComesWithItsServiceObjective.The seeder
SeedRightSizingScenarioAsynctakes an optionalvcoreCountfor this. It also gives an Azure SQL Database its ownmemory_statsmemory (167,117 MB) beside the host'sserver_propertiesmemory (933,836 MB).The memory rule's divisor
The Recommendations memory rule's finding reads
Memory over-provisioned (P95 SQL memory uses {ratio} of {n}GB RAM). Lite (LocalDataService.FinOps.Recommendations.cs) and the Darling Viewer (ViewerDataService.FinOps.Recommendations.cs) each divide byutil.PhysicalMemoryMb. That value comes frommemory_stats, never fromserver_properties.physical_memory_mb. The rule has also skipped an Azure SQL Database since #4877, so it needed no product change.Two tests pin it: the divisor must stay the
memory_statsfigure, and the rules file must read noserver_propertiesmemory column. A third seeds a large Azure SQL Database and expects no memory advice. The retired Dashboard underdeprecated/is unchanged.Left alone, and why
memory_stats. On an Azure SQL Database they are the database's own, so it needs no change.sys.dm_db_resource_stats, its grants come from the resource semaphores, and its worker term cannot fire becausecurrent_workers_countis NULL on this edition.sys.dm_os_sys_info.max_workers_count. On an Azure SQL Database that is the database's own setting (479 on the 1-vCore database), not a host figure, so it needs no change.Lite/Analysis/DuckDbFactCollector.Config.csandDarling/PerformanceMonitor.Darling.Analysis/PgFactCollector.Config.cs). The plan viewer's Server Context card has the same gap: its Hardware row shows the stored CPU count and memory (PerformanceMonitor.PlanAnalysis/ServerContextCard.cs). On an Azure SQL Database its memory is the host's, and its CPU count is the scheduler count, not the vCores. Neither is part of this change.memory_stats.total_physical_memory_mb. On an Azure SQL Database that is the database's memory limit, so they need no change.wwwrootis unchanged.Test plan
AzureSqlDatabaseclasses in each project,CpuAttributionTests,HealthCalculatorTests,FinOpsTests,FinOpsVerdictSourcePinTests,FinOpsRowDisplayZoneTests,ProvisioningVerdictTests,ViewerFinOpsRecommendationsTests,HourlyAttributionSpanTestsandMcpPayloadContractCensusTests.AzureSqlDatabaseMemoryScopeTestshas 28 tests in Lite.Tests and 24 in Darling.Tests. Lite runs the reads against a seeded DuckDB. Darling pins its PostgreSQL reads as SQL text, because no PostgreSQL ran.RightSizedSentence_OnAzureSqlDatabase_CitesTheBufferPoolShare_OfTheDatabasesMemoryLimit: the sentence ended at "p95 62.0%). No action needed." with no share.OverProvisionedSentence_OnAzureSqlDatabase_CitesTheBufferPoolShare_OfTheDatabasesMemoryLimit: the sentence went from "max 11%)" straight to "This database may have".FinOpsUtilizationCard_ShowsPhysicalMemoryAndTheBufferPoolShare_OnEveryEdition: not foundMemoryRatioText.Text = $"{bpPct:N0}%";(the Viewer name isFinOpsMemoryRatioText).HealthScore_OnAzureSqlDatabase_CarriesTheMemoryTermScoredFromMemoryStats: Expected 98, Actual 97.HealthScore_DoesNotDependOnTheEngineEdition(engineEdition: 5): Expected 98, Actual 97.MemoryRecommendation_NeverDividesByServerPropertiesMemory_SoItCannotPrintTheHostsGigabytes: foundphysical_memory_mbin the old comment.MemoryAndVmRules_OnAzureSqlDatabase_SayWhyTheyStandDown_AndDoNotCallTheMemoryTheHosts: found "reports the HOST's memory".server_properties: 7 red.UtilizationRead_TakesTheMemoryFiguresFromMemoryStats_OnEveryEditionfor editions 5, 3 and 8 (Expected 1838, Actual 933836).HealthScore_OnAzureSqlDatabase_CarriesTheMemoryTermScoredFromMemoryStats(Expected 98, Actual 86).HealthScore_OnSqlServerAndManagedInstance_IsTheSameScoreFromTheSameMemoryStatsfor editions 3 and 8 (Expected 98, Actual 86).UtilizationRead_DoesNotReadServerPropertiesForMemory.server_propertiesread takesphysical_memory_mbfrommemory_stats: 2 red,ServerPropertiesReads_OnAzureSqlDatabase_StayTheHostsAndNull_WhateverMemoryStatsHoldsandServerPropertiesReads_OnSqlServer_KeepTheirHardware(Expected 933836, Actual 1838).get_server_propertiespayload and in the inventory row: 5 red. They are the twoGetServerProperties_OnAzureSqlDatabase_...pins,LatestServerProperties_ReadsTheVcoreCountTheCollectorStored,InventoryRow_OnAzureSqlDatabase_LeavesTheMemoryAndCoreCellsBlank_AndSaysWhy(933888 where null was expected) andServerPropertiesReads_OnAzureSqlDatabase_StayTheHostsAndNull_WhateverMemoryStatsHolds.server_properties: 1 red,UtilizationRead_TakesTheMemoryFiguresFromMemoryStats_NeverFromServerProperties.physical_memory_mbfrommemory_stats: 1 red,ServerPropertiesReads_TakeTheirHardwareFromServerProperties_NeverFromMemoryStats(found "memory_stats").PhysicalMemoryCaption_NamesTheDatabasesLimit_OnAzureSqlDatabase_AndPhysicalMemoryEverywhereElse(Expected "Memory limit: ", Actual "Physical: ").FinOpsUtilizationCard_ExplainsTheBufferPoolShareForADatabase_InTheWordsBothAppsUse. The captionx:Nameand the caption assignment are pinned byFinOpsUtilizationCard_AsksTheSharedRule_ForTheWordsAroundItsMemoryFigures. The true reason in each of the two rule comments is pinned byMemoryAndVmRules_OnAzureSqlDatabase_SayWhyTheyStandDown_AndDoNotCallTheMemoryTheHosts. A finding that divides by another figure, and a rules file that mentionsphysical_memory_mb, both failMemoryRecommendation_NeverDividesByServerPropertiesMemory_SoItCannotPrintTheHostsGigabytes.AzureSqlDatabase_MemoryAndVmRightSizingAdviseNothing_BecauseItsMemoryComesWithItsServiceObjectivefailed because the rule advised. In Darling,MemoryAndVmRightSizing_StandDownOnAzureSqlDatabasedid not find the gate.OwnCpuCount,CpuCountText,CpuAttribution.Computeand the utilization read ignore the edition, which is the old behavior, and ranAzureSqlDatabaseHostMathTestsandCpuAttributionTests. Lite.Tests had 7 red of 39 and Darling.Tests had 6 red of 37. These checks failed.Attribution_OnAzureSqlDatabase_WithVcores_...gave Expected 1800, Actual 3600.Attribution_OnAzureSqlDatabase_WithNoVcores_...for null and 0 gave a value of 3600 where null was expected.OwnCpuCount_...gave Expected 1, Actual 2.CpuCountText_OnAzureSqlDatabase_...gave Expected "n/a", Actual "0". In Lite,UtilizationRead_ResolvesTheCpuCountThroughTheEdition(engineEdition: 5, vcoreCount: null, expectedCpuCount: 0)gave Expected 0, Actual 2, and the card pin failed on the missingCASEtext. In Darling, the read pin did not find theCASEtext.Which component(s) does this affect?
Checklist
dotnet build -c Debug)Changelog entry
The changelog line is written from this entry at release, so
CHANGELOG.mdis not edited here.SECTION: Fixed
ENTRY:
REF:
[Azure SQL Database CPU math uses the vCores, and the Utilization card shows its memory figures #4879]: Azure SQL Database CPU math uses the vCores, and the Utilization card shows its memory figures #4879