microsoft / microsoft/finops-toolkit
monthly-cost-change-percentage returns inf when the prior month is zero
Nobody has claimed this yet.
- Dominant language
- PowerShell
- Stars
- 603
- Forks
- 248
- Avg merge
- 7d 11h
- Merged PRs (30d)
- 11
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.kqlondev - 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/queries/catalog/monthly-cost-change-percentage.kql and inspect the guards around PreviousBilledCost and PreviousEffectiveCost. Reproduce the zero-prior-month case with the standalone datatable example from the issue. Done means both zero-prior-cost cases return 0.0 instead of inf while the first-row behavior remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure
- Domain
- analytics, data
- Issue type
- Bug
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 90/100