You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Azure Hybrid Benefit (AHB) columns added in hub 0.12 β x_SkuLicenseQuantity, x_SkuLicenseStatus, x_SkuLicenseType, x_SkuLicenseUnit β are computed in four independent places that have drifted apart. Three defects follow from that:
SQL Server gets the Windows Server 8-core minimum (should be 4).
Rows with no AHB eligibility get a license quantity on the FOCUS 1.0 ingestion path.
x_SkuLicenseStatus casing differs by ingestion path, and the dashboard filters on it case-sensitively.
Defect 1: SQL Server uses the Windows Server minimum of 8 cores
x_SkuLicenseQuantity applies a minimum of 8 core licenses to all license types. That is the Windows Server AHB rule; the SQL Server AHB minimum is 4 core licenses per VM. None of the four implementations branch on the license type β all use the same shape:
This is a floor of 8, not a fixed value β above 8 cores the actual core count is used. So only SQL Server rows with 2β7 cores are affected:
x_SkuCoreCount
Hub reports
Correct for SQL Server
Delta
2
8
4
+4
4
8
4
+4
6
8
6
+2
β₯ 8
= core count
= core count
0
Windows Server rows are correct today and must stay at a minimum of 8 β a global change to 4 would just move the error to Windows Server.
Defect 2: Non-AHB rows get a license quantity on the FOCUS 1.0 path
The copy in HubSetup_v1_2.kql (L162-L167) β the FOCUS 1.0 to 1.2 upgrade path inside Costs_v1_2() β omits the isempty(x_SkuLicenseType) guard that the ingestion copy has:
Every row with a core count therefore gets an x_SkuLicenseQuantity and x_SkuLicenseUnit = 'Cores', including Linux VMs with no AHB eligibility at all. That copy's x_SkuLicenseType detection is also narrower than the ingestion copy's (no x_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses') / has 'Windows' branch), so simply adding the guard is not sufficient β the two type detections should be aligned.
Defect 3: x_SkuLicenseStatus casing differs by path, and consumers filter case-sensitively
The same status value is written with different casing depending on where it is computed:
The hub dashboard filters this column with the case-sensitive == operator β three occurrences of x_SkuLicenseStatus == 'Not Enabled' and six of == 'Enabled' in dashboard.json. Rows coming through the FOCUS 1.0 upgrade path carry 'Not enabled' and therefore never match, so they are silently dropped from the Eligible resources and Eligible vCPU capacity tiles.
Per the repository content standard (sentence casing except proper nouns), 'Not enabled' is the correct spelling and the FOCUS 1.2 ingestion path is the outlier β which is unfortunately the path that writes the most data.
Downstream impact
Defect 1 is not only a column value. Both the ADX dashboard and the Power BI datasets derive unused-capacity metrics from it:
The AHB page of the hub dashboard surfaces this directly, so an inflated quantity inflates the reported waste. For a 2-vCPU SQL Server VM the dashboard reports 6 unused vCPUs where the correct figure is 2:
Windows Server output stays bit-identical to today; only SQL Server rows with 2β7 cores change.
Defect 2 β x_SkuLicenseQuantity and x_SkuLicenseUnit should be empty when x_SkuLicenseType is empty, on every path. Align the x_SkuLicenseType detection between the two KQL copies at the same time.
Defect 3 β standardize every producer on the sentence-cased 'Not enabled'. Because the FOCUS 1.2 path has already written 'Not Enabled' into Costs_final_v1_2 (see the note on reprocessing below), consumers should also switch from == to =~ so both spellings keep matching during the transition. The 'Not supported' value emitted only by the storage Power BI dataset should either be adopted everywhere or dropped.
π§ Environment
FinOps hub version: 0.12 and later (the columns were added in 0.12; the logic is unchanged in 12.0, 13.0, 14.0 and latest)
Cost Management export: FOCUS 1.0 and 1.2-preview (both ingestion paths are affected)
Applies to: ADX/Data Explorer deployments, Fabric RTI deployments, the ADX dashboard, and both Power BI datasets (KQL and storage-based)
Note on reprocessing
Costs_final_v1_2 is a materialized table populated by an update policy from Costs_raw (IngestionSetup_v1_2.kql L1077), so that transform runs at ingestion time and existing rows keep the old values. Fixing the KQL only changes newly ingested data unless the data is reprocessed. The HubSetup_v1_2.kql copy lives in the Costs_v1_2()function, so that one takes effect at query time and applies retroactively to FOCUS 1.0 data.
π Files to update
The four implementations differ from each other, so this is not a single-line change:
src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_2.kql (L685-L703) β the main FOCUS 1.2 transform (defects 1 and 3).
src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_v1_2.kql (L152-L168) β the FOCUS 1.0 to 1.2 upgrade path (defects 1, 2 and 3).
src/templates/finops-hub/dashboard.json β three x_SkuLicenseStatus == 'Not Enabled' filters and six == 'Enabled' filters to switch to =~ (defect 3).
src/power-bi/storage/Shared.Dataset/definition/tables/Costs.tmdl (L1698-L1705) β a Power Query (M) reimplementation of the same rules. This dataset has no x_SkuLicenseType column at all, only x_SkuLicenseStatus, so the SQL Server case has to be derived (from tmp_SQLAHB / x_SkuMeterSubcategory) before the minimum can be applied (defects 1 and 3).
src/queries/catalog/costs-enriched-base.kql (L54-L62) β same status/type logic again, and it does not compute x_SkuLicenseQuantity at all (defect 3, plus a consistency check).
src/queries/finops-hub-database-guide.md (L306-L313) β documents a different, older banding (8, then 16, then 24 for 20 cores, then actual) that no longer matches any shipped transform and has the same SQL Server issue. The surrounding x_SkuLicenseStatus / x_SkuLicenseType snippets have also drifted (different branch order, contains where the current code uses has).
src/templates/agent-plugin/agents/ftk-database-query.agent.md (L150) β documents the value set as Enabled, Not enabled; confirm it matches after standardizing.
Generated artifacts under release/ (finops-hub/modules/.../IngestionSetup_v1_2.kql, finops-hub/modules/.../HubSetup_v1_2.kql, finops-hub-fabric-setup-Ingestion.kql, finops-hub-fabric-setup-Hub.kql, finops-hub-dashboard.json) are rebuilt from the templates by Package-Toolkit.
Consider consolidating the KQL copies so each rule lives in one place.
Decide whether existing Costs_final_v1_2 data should be reprocessed (see the note above).
For SQL Server, actual license consumption also depends on the edition (1 Enterprise core license covers 4 vCPUs of Standard edition on Azure VMs). The edition is not reliably available in the cost data, so x_SkuLicenseQuantity stays a vCPU-based approximation β just with the correct minimum.
πββοΈ Ask for the community
We could use your help:
Please vote this issue up (π) to prioritize it.
If you use SQL Server Azure Hybrid Benefit, let us know whether the 4-core minimum matches how you track license consumption.
π Problem
The Azure Hybrid Benefit (AHB) columns added in hub 0.12 β
x_SkuLicenseQuantity,x_SkuLicenseStatus,x_SkuLicenseType,x_SkuLicenseUnitβ are computed in four independent places that have drifted apart. Three defects follow from that:x_SkuLicenseStatuscasing differs by ingestion path, and the dashboard filters on it case-sensitively.Defect 1: SQL Server uses the Windows Server minimum of 8 cores
x_SkuLicenseQuantityapplies a minimum of 8 core licenses to all license types. That is the Windows Server AHB rule; the SQL Server AHB minimum is 4 core licenses per VM. None of the four implementations branch on the license type β all use the same shape:This is a floor of 8, not a fixed value β above 8 cores the actual core count is used. So only SQL Server rows with 2β7 cores are affected:
x_SkuCoreCountWindows Server rows are correct today and must stay at a minimum of 8 β a global change to 4 would just move the error to Windows Server.
Defect 2: Non-AHB rows get a license quantity on the FOCUS 1.0 path
The copy in
HubSetup_v1_2.kql(L162-L167) β the FOCUS 1.0 to 1.2 upgrade path insideCosts_v1_2()β omits theisempty(x_SkuLicenseType)guard that the ingestion copy has:Every row with a core count therefore gets an
x_SkuLicenseQuantityandx_SkuLicenseUnit = 'Cores', including Linux VMs with no AHB eligibility at all. That copy'sx_SkuLicenseTypedetection is also narrower than the ingestion copy's (nox_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses')/has 'Windows'branch), so simply adding the guard is not sufficient β the two type detections should be aligned.Defect 3:
x_SkuLicenseStatuscasing differs by path, and consumers filter case-sensitivelyThe same status value is written with different casing depending on where it is computed:
IngestionSetup_v1_2.kql(L694) β FOCUS 1.2 ingestion'Not Enabled'HubSetup_v1_2.kql(L159) β FOCUS 1.0 upgrade'Not enabled'src/power-bi/storage/Shared.Dataset/.../Costs.tmdl(L1705)'Not enabled', plus a third value'Not supported'that no other path emitssrc/queries/catalog/costs-enriched-base.kql(L56)'Not enabled'The hub dashboard filters this column with the case-sensitive
==operator β three occurrences ofx_SkuLicenseStatus == 'Not Enabled'and six of== 'Enabled'indashboard.json. Rows coming through the FOCUS 1.0 upgrade path carry'Not enabled'and therefore never match, so they are silently dropped from the Eligible resources and Eligible vCPU capacity tiles.Per the repository content standard (sentence casing except proper nouns),
'Not enabled'is the correct spelling and the FOCUS 1.2 ingestion path is the outlier β which is unfortunately the path that writes the most data.Downstream impact
Defect 1 is not only a column value. Both the ADX dashboard and the Power BI datasets derive unused-capacity metrics from it:
| extend x_SkuLicenseUnusedQuantity = x_SkuLicenseQuantity - x_SkuCoreCountThe AHB page of the hub dashboard surfaces this directly, so an inflated quantity inflates the reported waste. For a 2-vCPU SQL Server VM the dashboard reports 6 unused vCPUs where the correct figure is 2:
sum(x_SkuLicenseUnusedQuantity)) β overstatedsum(x_SkuLicenseQuantity)) β overstatedπ£ Repro steps
Defect 1
Standard_D2s_v5orStandard_D4s_v5).Costsfunction in the hub's ADX/Fabric database:x_SkuLicenseQuantity = 8, regardless of the actual core count.Defect 2 β with FOCUS 1.0 data ingested, non-Windows/non-SQL rows carry a license quantity:
Defect 3 β both spellings coexist in the same table:
π€ Expected
Defect 1 β apply the minimum that matches the license type:
The license type is already available at that point in the pipeline:
Windows Server output stays bit-identical to today; only SQL Server rows with 2β7 cores change.
Defect 2 β
x_SkuLicenseQuantityandx_SkuLicenseUnitshould be empty whenx_SkuLicenseTypeis empty, on every path. Align thex_SkuLicenseTypedetection between the two KQL copies at the same time.Defect 3 β standardize every producer on the sentence-cased
'Not enabled'. Because the FOCUS 1.2 path has already written'Not Enabled'intoCosts_final_v1_2(see the note on reprocessing below), consumers should also switch from==to=~so both spellings keep matching during the transition. The'Not supported'value emitted only by the storage Power BI dataset should either be adopted everywhere or dropped.π§ Environment
latest)Note on reprocessing
Costs_final_v1_2is a materialized table populated by an update policy fromCosts_raw(IngestionSetup_v1_2.kql L1077), so that transform runs at ingestion time and existing rows keep the old values. Fixing the KQL only changes newly ingested data unless the data is reprocessed. TheHubSetup_v1_2.kqlcopy lives in theCosts_v1_2()function, so that one takes effect at query time and applies retroactively to FOCUS 1.0 data.π Files to update
The four implementations differ from each other, so this is not a single-line change:
src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/IngestionSetup_v1_2.kql(L685-L703) β the main FOCUS 1.2 transform (defects 1 and 3).src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/scripts/HubSetup_v1_2.kql(L152-L168) β the FOCUS 1.0 to 1.2 upgrade path (defects 1, 2 and 3).src/templates/finops-hub/dashboard.jsonβ threex_SkuLicenseStatus == 'Not Enabled'filters and six== 'Enabled'filters to switch to=~(defect 3).src/power-bi/storage/Shared.Dataset/definition/tables/Costs.tmdl(L1698-L1705) β a Power Query (M) reimplementation of the same rules. This dataset has nox_SkuLicenseTypecolumn at all, onlyx_SkuLicenseStatus, so the SQL Server case has to be derived (fromtmp_SQLAHB/x_SkuMeterSubcategory) before the minimum can be applied (defects 1 and 3).src/queries/catalog/costs-enriched-base.kql(L54-L62) β same status/type logic again, and it does not computex_SkuLicenseQuantityat all (defect 3, plus a consistency check).src/queries/finops-hub-database-guide.md(L306-L313) β documents a different, older banding (8, then 16, then 24 for 20 cores, then actual) that no longer matches any shipped transform and has the same SQL Server issue. The surroundingx_SkuLicenseStatus/x_SkuLicenseTypesnippets have also drifted (different branch order,containswhere the current code useshas).src/templates/agent-plugin/agents/ftk-database-query.agent.md(L150) β documents the value set asEnabled, Not enabled; confirm it matches after standardizing.release/(finops-hub/modules/.../IngestionSetup_v1_2.kql,finops-hub/modules/.../HubSetup_v1_2.kql,finops-hub-fabric-setup-Ingestion.kql,finops-hub-fabric-setup-Hub.kql,finops-hub-dashboard.json) are rebuilt from the templates byPackage-Toolkit.Costs_final_v1_2data should be reprocessed (see the note above).βΉοΈ Additional context
Licensing references
Known remaining limitation after the fix
For SQL Server, actual license consumption also depends on the edition (1 Enterprise core license covers 4 vCPUs of Standard edition on Azure VMs). The edition is not reliably available in the cost data, so
x_SkuLicenseQuantitystays a vCPU-based approximation β just with the correct minimum.πββοΈ Ask for the community
We could use your help: