Skip to content

monthly-cost-change-percentage returns inf when the prior month is zero #2323

Open

Description

🐛 Problem

src/queries/catalog/monthly-cost-change-percentage.kql guards its division with isempty(PreviousBilledCost).

isempty() is true for the null that prev() returns on the first row, but it is false for 0.0. A month whose prior month summed to exactly zero therefore falls through the guard, and KQL evaluates x / 0.0 as inf rather than raising an error — so an infinity is returned to the caller as a percentage.

| project ChargePeriodStart,
    BilledCostChangePct = iff(isempty(PreviousBilledCost), 0.0, toreal((BilledCost - PreviousBilledCost) * 100.0 / PreviousBilledCost)),
    EffectiveCostChangePct = iff(isempty(PreviousEffectiveCost), 0.0, toreal((EffectiveCost - PreviousEffectiveCost) * 100.0 / PreviousEffectiveCost))

👣 Repro steps

No hub required — the shape of the pipeline is enough:

datatable(ChargePeriodStart:datetime, BilledCost:real, EffectiveCost:real)
[
    datetime(2025-01-01), 100.0, 100.0,
    datetime(2025-02-01),   0.0,  80.0,
    datetime(2025-03-01),  50.0, 120.0,
]
| order by ChargePeriodStart asc
| extend PreviousBilledCost = prev(BilledCost)
| project ChargePeriodStart,
    BilledCostChangePct = iff(isempty(PreviousBilledCost), 0.0, toreal((BilledCost - PreviousBilledCost) * 100.0 / PreviousBilledCost))
ChargePeriodStart BilledCostChangePct
2025-01-01 0
2025-02-01 -100
2025-03-01 inf

Is a zero month reachable?

Yes, and BilledCost is the likelier of the two to hit it. On usage rows covered by a commitment discount BilledCost is zero — the charge was billed at purchase time, and the amortized amount lands in EffectiveCost instead. Any scope whose month is entirely commitment-covered therefore sums BilledCost to exactly zero, and the following month's BilledCostChangePct comes back as inf.

Scoping the query with an extra | where is a normal way to use it, which makes this easier to reach than the estate-wide default suggests.

🤔 Expected

The same value the guard already returns for the undefined case — 0.0 — rather than an infinity.

The == 0.0 form is already the convention elsewhere in the catalog, in service-price-benchmarking.kql:

x_CommitmentDiscountPercent = iif(ContractedCost == 0.0, 0.0, toreal(ContractedCost - EffectiveCost) * 100.0 / toreal(ContractedCost)),

🔧 Environment

  • Affected file: src/queries/catalog/monthly-cost-change-percentage.kql on dev
  • FinOps hub version: any — reproducible standalone with datatable, no ingestion involved
  • Billing account type: any
  • Cost Management export: any

ℹ️ Additional context

A separate, arguable point that I have deliberately left out of the fix: returning 0.0 when there is no prior month conflates "no change" with "no data to compare against". real(null) would distinguish them, but that is a behaviour change for existing consumers, so the PR keeps the current convention and only closes the divide-by-zero hole. Happy to follow up if you would prefer the stricter version.

Activity

  1. microsoft-github-policy-service commented on Sep 16, 2026

    @microsoft-github-policy-service

    Thank you for submitting this issue! We try to respond within 3 business days. In the meantime:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Micro PR 🔬Very small PR that should be especially easy for newcomersSkill: KQLKQL queries and Data Explorer integrationStatus: 🔬 Code reviewIssue is currently in code review with open PRsTool: FinOps hubsData pipeline solutionType: Bug 🐛Something isn't working

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions