Skip to content

fix: make Azure billing usage and forecast windows configurable - #52209

Open
stefans-elastic wants to merge 3 commits into
elastic:mainfrom
stefans-elastic:fix/azure_billing_make_lookback_window_configurable
Open

fix: make Azure billing usage and forecast windows configurable#52209
stefans-elastic wants to merge 3 commits into
elastic:mainfrom
stefans-elastic:fix/azure_billing_make_lookback_window_configurable

Conversation

@stefans-elastic

@stefans-elastic stefans-elastic commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Proposed commit message

fix: make Azure billing usage and forecast windows configurable

The azure/billing metricset always queried a hardcoded window: exactly the
previous full UTC day for usage, and reference-2d..+30d for forecast. Azure
Cost Management data can change for up to 72 hours after a billing period
closes, but the metricset never re-queried a date it had already collected,
so late cost corrections were never ingested.

Add two duration config options to the shared azure.Config:

  • billing_usage_lookback (default 24h) how far back to (re)query usage
  • billing_forecast_window (default 720h) length of the forecast period

usageIntervalFrom and forecastIntervalFrom now take the window as a
parameter; defaults reproduce the previous behaviour exactly, so existing
deployments are unaffected. Operators can set billing_usage_lookback: 72h
to re-ingest Azure's late corrections.

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have made corresponding change to the default configuration files
  • I have added tests that prove my fix is effective or that my feature works. Where relevant, I have used the stresstest.sh script to run them under stress conditions and race detector to verify their stability.
  • I have added an entry in ./changelog/fragments using the changelog tool.

Disruptive User Impact

How to test this PR locally

Ran the azure billing metricset against a live Azure subscription with
billing_usage_lookback: 72h, publishing to a local Elasticsearch, and confirmed
that usage data is now collected for the whole lookback window instead of just the
previous day.

The metricset logs show the widened usage window (start moved back to -72h, end
unchanged at the previous full day):

"message":"Getting usage details for scope","billing.usage.start_time":"2026-07-19T00:00:00.000Z","billing.usage.end_time":"2026-07-21T23:59:59.000Z"

Verified in Elasticsearch that usage documents span three full days. The
azure.billing.usage_start filter isolates usage docs (excludes the forward-looking
forecast events), and a daily date_histogram on azure.billing.usage_date returns
one bucket per collected day:

Request / Response

Request:

POST metricbeat-*/_search
{
  "size": 0,
  "query": {
    "bool": {
      "filter": [
        { "term":  { "tags": "azure-billing-e2e" } },
        { "exists":{ "field": "azure.billing.usage_start" } }
      ]
    }
  },
  "aggs": {
    "days": {
      "date_histogram": {
        "field": "azure.billing.usage_date",
        "calendar_interval": "day",
        "format": "yyyy-MM-dd"
      }
    }
  }
}

Response:

{
  "took": 8,
  "timed_out": false,
  "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 },
  "hits": { "total": { "value": 839, "relation": "eq" }, "max_score": null, "hits": [] },
  "aggregations": {
    "days": {
      "buckets": [
        { "key_as_string": "2026-07-19", "key": 1784419200000, "doc_count": 277 },
        { "key_as_string": "2026-07-20", "key": 1784505600000, "doc_count": 289 },
        { "key_as_string": "2026-07-21", "key": 1784592000000, "doc_count": 273 }
      ]
    }
  }
}

Three usage_date buckets (2026-07-19, 2026-07-20, 2026-07-21) confirm the
72h window re-queried three full days. With the default 24h lookback the same
query returns a single bucket (2026-07-21), matching the previous behaviour.

A reviewer can reproduce this with a minimal config:

metricbeat.modules:
- module: azure
  metricsets: [billing]
  enabled: true
  period: 24h
  client_id: '${AZURE_CLIENT_ID}'
  client_secret: '${AZURE_CLIENT_SECRET}'
  tenant_id: '${AZURE_TENANT_ID}'
  subscription_id: '${AZURE_SUBSCRIPTION_ID}'
  billing_usage_lookback: 72h

Related issues

Use cases

Screenshots

Logs

@stefans-elastic stefans-elastic self-assigned this Jul 22, 2026
@stefans-elastic stefans-elastic added Team:Obs-InfraObs Label for the Observability Infrastructure Monitoring team bugfix labels Jul 22, 2026
@botelastic botelastic Bot added needs_team Indicates that the issue/PR needs a Team:* label and removed needs_team Indicates that the issue/PR needs a Team:* label labels Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 GitHub comments

Just comment with:

  • run docs-build : Re-trigger the docs validation. (use unformatted text in the comment!)
  • /test : Run the Buildkite pipeline.

@stefans-elastic
stefans-elastic force-pushed the fix/azure_billing_make_lookback_window_configurable branch from 0b9cdd2 to 5afc628 Compare July 22, 2026 13:47
@mergify

mergify Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

This pull request does not have a backport label.
If this is a bug or security fix, could you label this PR @stefans-elastic? 🙏.
For such, you'll need to label your PR with:

  • The upcoming major version of the Elastic Stack
  • The upcoming minor version of the Elastic Stack (if you're not pushing a breaking change)

To fixup this pull request, you need to add the backport labels for the needed
branches, such as:

  • backport-8./d is the label to automatically backport to the 8./d branch. /d is the digit
  • backport-active-all is the label that automatically backports to all active branches.
  • backport-active-8 is the label that automatically backports to all active minor branches for the 8 major.
  • backport-active-9 is the label that automatically backports to all active minor branches for the 9 major.

@stefans-elastic
stefans-elastic force-pushed the fix/azure_billing_make_lookback_window_configurable branch from 5afc628 to 9e64196 Compare July 22, 2026 14:08
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown
Contributor

✅ Elastic Docs Style Checker (Vale)

No issues found on modified lines!


The Vale linter checks documentation changes against the Elastic Docs style guide. To use Vale locally or report issues, refer to Elastic style guide for Vale.

@stefans-elastic

Copy link
Copy Markdown
Contributor Author

/test

@stefans-elastic
stefans-elastic marked this pull request as ready for review July 23, 2026 11:32
@stefans-elastic
stefans-elastic requested review from a team as code owners July 23, 2026 11:32

@jakubgalecki0 jakubgalecki0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function func (client *Client) GetMetrics sets following filter:

       filter := fmt.Sprintf(
		"properties/usageStart eq '%s' and properties/usageEnd eq '%s'",
		timeOpts.usageStart.Format(time.RFC3339Nano),
		timeOpts.usageEnd.Format(time.RFC3339Nano),
	)

	result, err := client.BillingService.GetUsageDetails(
		scope,
		"properties/meterDetails",
		filter,
		armconsumption.MetrictypeActualCostMetricType,
		timeOpts.usageStart.Format("2006-01-02"), // startDate
		timeOpts.usageEnd.Format("2006-01-02"),   // endDate
	)

Will this change work with that or should we use ge and le? We are passing start and end separately but it's worth checking out.

Use range comparisons for the Azure Consumption usage details filter so configurable multi-day lookback windows work for legacy billing scopes as well as the startDate/endDate path.

Add a focused unit test that verifies the generated usage details filter and request dates for a multi-day window.

Assisted-By: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
@stefans-elastic

Copy link
Copy Markdown
Contributor Author

@jakubgalecki0 Verified this and changed the usage details $filter to use a range: properties/usageStart ge ... and properties/usageEnd le .... I kept startDate/endDate in the request for the modern account path and added TestClientUsesRangeFilterForUsageDetails to assert the generated filter plus request dates for a multi-day lookback. Pushed in 119be81.


If none of the 2 options are entered then the subscription ID will be used as scope.

`billing_usage_lookback`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which version will these config options become available?

If this will not be backported:

Suggested change
`billing_usage_lookback`
`billing_usage_lookback` {applies_to}`stack: ga 9.6+`

If it will be backported, then show the earliest version in which it will become available, e.g:

Suggested change
`billing_usage_lookback`
`billing_usage_lookback` {applies_to}`stack: ga 9.3+`

data is documented to be updated for up to 72 hours after a billing period closes, so increasing
this to `72h` ensures that late cost corrections from Azure are re-queried and ingested.

`billing_forecast_window`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here… If this will not be backported:

Suggested change
`billing_forecast_window`
`billing_forecast_window` {applies_to}`stack: ga 9.6+`

If it will be backported, then show the earliest version in which it will become available, e.g:

Suggested change
`billing_forecast_window`
`billing_forecast_window` {applies_to}`stack: ga 9.3+`

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

Labels

bugfix Team:Obs-InfraObs Label for the Observability Infrastructure Monitoring team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants