Skip to content

fix: treat prometheus counters as rates in autoscaling signals#1042

Open
nXtCyberNet wants to merge 9 commits into
volcano-sh:mainfrom
nXtCyberNet:issue/counter
Open

fix: treat prometheus counters as rates in autoscaling signals#1042
nXtCyberNet wants to merge 9 commits into
volcano-sh:mainfrom
nXtCyberNet:issue/counter

Conversation

@nXtCyberNet

Copy link
Copy Markdown
Contributor

What type of PR is this?
/kind bug
/kind enhancement
What this PR does / why we need it:

Prometheus counter metrics are monotonically increasing cumulative values.
The autoscaler was treating these raw cumulative values as instantaneous load
signals, causing two critical failures:

  1. Scale-up runaway: A pod that handled 50 total requests since startup would
    trigger scaling to 5 replicas (ceil(50 / target)), regardless of current traffic.

  2. No scale-down: Even after traffic stops, the counter value persists at 50,
    preventing the autoscaler from ever scaling back down.

The fix: Track per-pod counter snapshots across scrape cycles and compute the
rate of change (delta/elapsed_seconds) instead of the raw cumulative value.
This correctly reflects instantaneous demand and enables both scale-up and scale-down.

Implementation Details:

  • Added CounterMap and ScrapeTimestamp fields to HistogramInfo to maintain
    per-pod/metric baseline state across scrape cycles.
  • New rate calculation in metric collection:
rate = (current_value - previous_value) / elapsed_seconds
  • Counter resets (current < previous) detected and handled by clamping rate to 0.
  • First scrape returns rate=0 until baseline is established.
  • Added GetLastUnfreshSnapshotWithTimestamp() to SnapshotSlidingWindow
    to expose precise per-pod scrape timestamps (more accurate than window-level timestamps).
  • Backward compatibility: Nil CounterMap guards protect in-memory snapshots
    created before this change.

Which issue(s) this PR fixes:
Fixes #1037

Special notes for your reviewer:

  • Why per-pod timestamps? The sliding-window-level timestamp is too coarse;
    per-pod scrape times give us the actual elapsed duration for each counter,
    improving rate precision when scrape intervals vary.

  • Counter reset handling: If a pod restarts, its counter resets to 0.
    Detecting current < previous avoids reporting a massive negative rate;
    returning 0 is safe and gives the pod a grace period to re-accumulate load signals.

  • Backward compatibility: Any in-memory snapshots from before this change
    will have CounterMap == nil. These are safely handled with a nil-check guard.

  • Testing recommendation: Add bench tests for rate calculation under
    varying scrape intervals and counter reset scenarios.

Does this PR introduce a user-facing change?:
Yes. Autoscaling behavior for counter-based metrics will change—scaling will now
respond to instantaneous rate of change rather than cumulative totals, enabling
proper scale-down behavior.

Fix autoscaler counter metric handling: Prometheus counter metrics are now 
correctly treated as rates (delta/elapsed_seconds) instead of raw cumulative 
values. This fixes runaway scale-up and enables proper scale-down when traffic stops.

Copilot AI review requested due to automatic review settings May 13, 2026 16:34
@volcano-sh-bot volcano-sh-bot added kind/bug kind/enhancement New feature or request labels May 13, 2026
@volcano-sh-bot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign git-malu for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request updates the metric collector to calculate rates for Prometheus counters by comparing current values with previous snapshots, introducing a new GetLastUnfreshSnapshotWithTimestamp method in the sliding window structure to track scrape intervals. The reviewer suggested improving the precision of rate calculations by using a single consistent timestamp per pod scrape rather than calling the timestamp function multiple times, and recommended refactoring GetLastUnfreshSnapshot to utilize the new method to reduce code duplication.

Comment on lines 188 to 194
collector.processPrometheusString(result, pastHistogramMap, pastCounterMap, currentHistogramMap, currentCounterMap, pastScrapeTimestamp, instanceInfo.MetricsMap)
(*currentHistograms)[pod.Name] = HistogramInfo{
PodStartTime: pod.Status.StartTime,
HistogramMap: currentHistogramMap,
PodStartTime: pod.Status.StartTime,
HistogramMap: currentHistogramMap,
CounterMap: currentCounterMap,
ScrapeTimestamp: util.GetCurrentTimestamp(),
}

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.

medium

For improved precision in rate calculation, it's better to determine the current scrape timestamp once per pod and use it consistently. Currently, util.GetCurrentTimestamp() is called inside processPrometheusString for each metric, and again when creating HistogramInfo. This can introduce minor inaccuracies because the timestamp used for the rate calculation (now) will be slightly different from the timestamp stored for the next cycle (ScrapeTimestamp).

To improve this, you can get the timestamp once before processing the metrics and use it in both places. This ensures the elapsed_seconds for the rate calculation is based on the exact interval between the stored scrape timestamps.

Example of the proposed change:

// In fetchMetricsFromPods:
...
result := string(bodyStr)
now := util.GetCurrentTimestamp()
collector.processPrometheusString(result, pastHistogramMap, pastCounterMap, currentHistogramMap, currentCounterMap, pastScrapeTimestamp, now, instanceInfo.MetricsMap)
(*currentHistograms)[pod.Name] = HistogramInfo{
    PodStartTime:    pod.Status.StartTime,
    HistogramMap:    currentHistogramMap,
    CounterMap:      currentCounterMap,
    ScrapeTimestamp: now,
}
...

// And update processPrometheusString signature and body:
func (c *MetricCollector) processPrometheusString(..., pastScrapeTimestamp int64, now int64, instanceMetricMap algorithm.Metrics) {
    // ...
    // inside case ..._COUNTER:
    // remove: now := util.GetCurrentTimestamp()
    // ...
}

Comment on lines +238 to +252
func (window *SnapshotSlidingWindow[T]) GetLastUnfreshSnapshotWithTimestamp() (value T, timestamp int64, ok bool) {
if window.freshMilliseconds == 0 {
return value, 0, false
}
currentTimestamp := window.getCurrentTimestamp()
window.expire(currentTimestamp)
if window.pool.Len() == 0 {
return value, 0, false
}
front := window.pool.Front()
if isFresh(window.freshMilliseconds, currentTimestamp, front.timestamp) {
return value, 0, false
}
return front.value, front.timestamp, true
}

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.

medium

The logic inside this new function is nearly identical to GetLastUnfreshSnapshot. To reduce code duplication and improve maintainability, GetLastUnfreshSnapshot could be refactored to call this new function and discard the returned timestamp. This would make the code more aligned with the DRY (Don't Repeat Yourself) principle.

@hzxuzhonghu

Copy link
Copy Markdown
Member

Thanks for the analysis, can you add some test coverage

@hzxuzhonghu

Copy link
Copy Markdown
Member

From another view, it reflects the current api does not fit all.

// AutoscalingPolicyMetric defines a metric and its target value for scaling decisions.
type AutoscalingPolicyMetric struct {
	// MetricName defines the name of the metric to monitor for scaling decisions.
	MetricName string `json:"metricName"`
	// TargetValue defines the target value for the metric that triggers scaling operations.
	TargetValue resource.Quantity `json:"targetValue"`
}

@nXtCyberNet

Copy link
Copy Markdown
Contributor Author

Hi @hzxuzhonghu, thanks for the review.
I apologize—I didn't fully consider the design implications. Before I proceed, I want to understand the roadmap better. Do you think adding a MetricType field to the API would be a good long-term solution? If you believe it's worth doing, I'm happy to include it in this PR.
However, if adding MetricType would conflict with the existing design (where histogram is the default), I think the best approach is to close this PR for now. Adding counter support without explicit API-level type declaration could introduce ambiguity and break existing behavior.
What are your thoughts on the right path forward?

@hzxuzhonghu

Copy link
Copy Markdown
Member

@nXtCyberNet I donot have a good suggestion now, but will deep dive into other scalers first

@nXtCyberNet

Copy link
Copy Markdown
Contributor Author

Okay, I'll wait for your response. In the meantime, I'll add the test coverage you requested. Thanks!

@nXtCyberNet

Copy link
Copy Markdown
Contributor Author

@hzxuzhonghu any updates ?

@nXtCyberNet

nXtCyberNet commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

@kube-gopher hi , just want to know your thoughts on this , since you already the Observability part of the project , Thanks!

@kube-gopher

Copy link
Copy Markdown
Member

@nXtCyberNet It looks feasible for now, but I haven't looked into it deeply yet. I'm planning to add a feature to the scaler later—I'll open an issue when the time comes, and we can discuss it together then!

@hzxuzhonghu hzxuzhonghu mentioned this pull request Jun 24, 2026
7 tasks
@hzxuzhonghu

Copy link
Copy Markdown
Member

can we finish it and make it release in v1.0 by end of this month

@kube-gopher

Copy link
Copy Markdown
Member

@hzxuzhonghu That should work. I'll run a test tomorrow and see how it goes

@nXtCyberNet

nXtCyberNet commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@hzxuzhonghu @kube-gopher , Yup okay I will complete it asap, Thanks for the confirmation.

@nXtCyberNet

nXtCyberNet commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Hi @hzxuzhonghu, thanks for the review. I apologize, I didn't fully consider the design implications. Before I proceed, I want to understand the roadmap better. Do you think adding a MetricType field to the API would be a good long-term solution? If you believe it's worth doing, I'm happy to include it in this PR. However, if adding MetricType would conflict with the existing design (where histogram is the default), I think the best approach is to close this PR for now. Adding counter support without an explicit API-level type declaration could introduce ambiguity and break existing behavior. What are your thoughts on the right path forward?

I think this issue may already be solved. Looking at https://github.com/volcano-sh/kthena/blob/main/docs/kthena/docs/reference/crd/workload.serving.volcano.sh.md#metricsource, it seems we already have a way to specify the required behavior.

@nXtCyberNet

Copy link
Copy Markdown
Contributor Author

@hzxuzhonghu could you please take a look. Thanks!

Comment on lines -298 to -301
currentHistogramMap := currentHistograms[pod.Name].HistogramMap
if currentHistogramMap == nil {
currentHistogramMap = make(map[string]*histogram.Snapshot)
}

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.

From a visual standpoint, the current design isn't as good as before

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yup i agree , however it didnot create very big change just reuse the already created map , i.e its just a memory optimization. But i will do this , Thanks for pointing out ,

Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
.
Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
}
currentHistograms := make(map[string]HistogramInfo)

pastCounters, ok := collector.PastCounters.GetLastUnfreshSnapshot()

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.

Have you considered this issue: when counting in a window-based manner, within the first minute after the collector starts or the Pod restarts, even though the counter itself is increasing, all auto-scaling metrics based on that counter will be reported as 0, potentially causing scaling-out to be skipped?

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.

My suggestion is to have the counter immediately obtain the baseline from the last scrape

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.

@nXtCyberNet I suggest that you ultimately submit a clean commit history

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yup i agree i willl rebase it later in single commit

@nXtCyberNet nXtCyberNet Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

but i want when the full system starts then the baseline is also 0 so why did it a problem , but if you thinking if the router is restarted then also since this metrics is gone so how can it get it and if the vllm pod crashed then also all the metrics get flushed out so , isnt it a current tradeoff , and also we are finding average rate over a minute not the instantaneus rate . so could you explain a bit more about this . and also didnot it gonna break the autoscaler algo as the rate we get is a noise
cc @hzxuzhonghu

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.

@nXtCyberNet I don't quite understand your language. Please express your thoughts in an affirmative tone or a declarative mood

@nXtCyberNet nXtCyberNet Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i will use ai to finetune this ,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion, but I have a few concerns:

On the "baseline = 0" issue: When the whole system starts fresh, the baseline is also 0 — so why is that actually a problem in that case?
On restarts/crashes: If the router restarts, its metrics are gone anyway. Same if the vLLM pod crashes — all its metrics get flushed. Isn't this just an inherent tradeoff we already have, regardless of the baseline approach? Could you explain in more detail what specific case you're worried about?
On time drift: If there's any time drift, the counter resets back to 0 as well — this happens in both of the above cases too.
On averaging: We're computing the average rate over a full minute, not the instantaneous rate. Won't that introduce noise into what the autoscaler sees, and potentially break its scaling logic?

cc @hzxuzhonghu

currentCounterMap[policyKey] = v
rate := 0.0
now := util.GetCurrentTimestamp()
if pastCounterMap != nil && pastScrapeTimestamp > 0 {

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.

With this logic, the first scrape after the collector starts or a pod restarts always reports 0 for counter metrics because there is no previous baseline yet. If traffic is already increasing during that first window, scale-out can be skipped until the next scrape. Please seed the baseline from the first scrape while still allowing the next scrape to produce a rate, or document why this delay is acceptable for autoscaling.

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.

@nXtCyberNet This is the best repair solution

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.

@nXtCyberNet Based on your previous response, I think you should be able to understand the maintainer's suggestion

@nXtCyberNet nXtCyberNet Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@kube-gopher i think i got it now ,

@kube-gopher kube-gopher Jul 8, 2026

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.

I'll state my view directly: seed on first scrape, compute from the next scrape, and keep the 60s
GetLastUnfreshSnapshot() logic only for histogram/SLO metrics

@nXtCyberNet nXtCyberNet Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

okay i have done this even with 15 sec scrap time the rate became noisey but that i think is not a big issue . now you can take a look . Thanks for help

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.

@nXtCyberNet The repair is incomplete

@kube-gopher

kube-gopher commented Jul 8, 2026

Copy link
Copy Markdown
Member

@nXtCyberNet Here's a question for you to think about: Prometheus has a default scrape interval of 15 seconds, and the counter snapshot staleness window only reverts to a "stale" baseline after 60 seconds

  1. What rate does the autoscaler report at t=15s, t=30s, t=45s, and t=60s while the counter is increasing?
  2. What rate does it report on the first reconcile after the baseline finally becomes eligible, for example at t=75s?
  3. During that initial 60s period, is the autoscaler observing real load or treating the metric as zero?

You may use AI to assist you, but for the final answer, I want you to phrase it in your own words for me

@nXtCyberNet

Copy link
Copy Markdown
Contributor Author

Hi, so for your answer, considering the case where the router pod restarts, this is how I understand the timeline:

t=0    collector starts
t=15   first snapshot stored (no baseline)
t=30   no baseline
t=45   no baseline
t=60   no baseline (the t=15 sample is only 45s old)
t=75   the t=15 sample becomes 60s old, so this is the first valid baseline

rate = (counter75 - counter15) / 60

So you're right that during this initial ~60s period the autoscaler will see the request rate as 0.

My question is whether this is actually a problem or simply the expected trade-off of the current design.

On a router restart, the collector loses its in-memory sliding window, so there is no previous sample available to compute a rate from. Similarly, if the vLLM pod restarts, the Prometheus counters themselves reset. In both cases, there isn't enough historical information to compute a meaningful rate immediately after restart.

Also, since this metric is intentionally defined as a rolling one-minute average rather than an instantaneous 15s rate, waiting until a full one-minute baseline exists seems consistent with what the metric is trying to represent.

So is the concern specifically that the autoscaler observes 0 instead of "unknown" during that warm-up period? Or is there another issue I'm missing?

@kube-gopher

Copy link
Copy Markdown
Member

Hi, so for your answer, considering the case where the router pod restarts, this is how I understand the timeline:

t=0    collector starts
t=15   first snapshot stored (no baseline)
t=30   no baseline
t=45   no baseline
t=60   no baseline (the t=15 sample is only 45s old)
t=75   the t=15 sample becomes 60s old, so this is the first valid baseline

rate = (counter75 - counter15) / 60

So you're right that during this initial ~60s period the autoscaler will see the request rate as 0.

My question is whether this is actually a problem or simply the expected trade-off of the current design.

On a router restart, the collector loses its in-memory sliding window, so there is no previous sample available to compute a rate from. Similarly, if the vLLM pod restarts, the Prometheus counters themselves reset. In both cases, there isn't enough historical information to compute a meaningful rate immediately after restart.

Also, since this metric is intentionally defined as a rolling one-minute average rather than an instantaneous 15s rate, waiting until a full one-minute baseline exists seems consistent with what the metric is trying to represent.

So is the concern specifically that the autoscaler observes 0 instead of "unknown" during that warm-up period? Or is there another issue I'm missing?

Currently, the issue revolves around the fact that it returns 0 instead of unknown

@nXtCyberNet nXtCyberNet requested a review from hzxuzhonghu July 8, 2026 14:00
Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
Copilot AI review requested due to automatic review settings July 8, 2026 14:51

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@kube-gopher kube-gopher left a comment

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.

you are adding a unit test case yourself

Comment on lines +351 to +369
if mf.GetType() == io_prometheus_client.MetricType_COUNTER {
currentCounterMap[policyKey] = v
rate := 0.0
now := util.GetCurrentTimestamp()
if pastCounterMap != nil && pastScrapeTimestamp > 0 {
if prev, ok := pastCounterMap[policyKey]; ok {
if v >= prev {
elapsedSec := float64(now-pastScrapeTimestamp) / 1000.0
if elapsedSec > 0 {
rate = (v - prev) / elapsedSec
}
}
}
}
values[policyKey] += rate
} else {
if gotValue {
values[policyKey] += v
}

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.

if mf.GetType() == io_prometheus_client.MetricType_COUNTER {
        if !gotValue {
                continue
        }
        currentCounterMap[policyKey] = v

        prev, ok := pastCounterMap[policyKey]
        if !ok || pastScrapeTimestamp <= 0 {
                continue
        }
        if v < prev {
                continue
        }

        elapsedSec := float64(util.GetCurrentTimestamp()-pastScrapeTimestamp) / 1000.0
        if elapsedSec <= 0 {
                continue
        }

        values[policyKey] += (v - prev) / elapsedSec
} else {
        if gotValue {
                values[policyKey] += v
        }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yup i agree 0 could cause fake baseline , instead insufficient metrics as it will already managed by the getDesiredInstancesForSingleInstanceMetric

err = collector.collectPodMetrics(context.Background(), pod, podSource, wanted, values, pastHistograms, currentHistograms, pastCounters, currentCounters)
require.NoError(t, err)

assert.Equal(t, 0.0, values["my_policy_key"])

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.

_, ok := values["my_policy_key"]
assert.False(t, ok)

err = collector.collectPodMetrics(context.Background(), pod, podSource, wanted, values, pastHistograms, currentHistograms, pastCounters, currentCounters)
require.NoError(t, err)

assert.InDelta(t, tc.want.rate, values["my_policy_key"], 1e-2)

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.

add

} else {
        _, ok := values["my_policy_key"]
        assert.False(t, ok)
}

@kube-gopher

Copy link
Copy Markdown
Member

@nXtCyberNet Here's my thinking

Comment on lines +381 to +386

currentCounters[pod.Name] = CounterInfo{
PodStartTime: pod.Status.StartTime,
CounterMap: currentCounterMap,
ScrapeTimestamp: util.GetCurrentTimestamp(),
}

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.

Counter-rate calculations are inaccurate when a pod is scraped through more than one metric source because the implementation retains only one timestamp per pod. suggest optimizing it

@nXtCyberNet nXtCyberNet Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree this would make the collector more robust. I do have one question, though.

My understanding was that for autoscaling, the counter metric would typically come directly from the vLLM metrics endpoint, which acts as the single source of truth. In that case, I would expect each pod to expose only one relevant counter source for the autoscaler.

Could you elaborate on the scenarios where we'd intentionally scrape counter metrics from multiple endpoints of the same pod? If both the vLLM container and a sidecar exposed a similar counter, wouldn't we generally want to rely on the vLLM metric as the authoritative source? I'm wondering if I'm missing a valid use case or misunderstanding the intended design.

I understand that the API allows this configuration, but I'm trying to understand the motivation and intended deployment scenarios for supporting it. I may be missing a use case here.

Signed-off-by: nXtCyberNet <rohantech2005@gmail.com>
Copilot AI review requested due to automatic review settings July 10, 2026 08:50

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

currentCounterMap[policyKey] = v

prev, ok := pastCounterMap[policyKey]
if !ok || pastScrapeTimestamp <= 0 {

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.

When one selected pod has no baseline, this only skips that pod. values can still contain the same policy key from another pod, so the autoscaler emits a partial rate as though every ready replica were represented. Please suppress the policy metric for the whole pod group until every selected pod has a valid delta, and cover the mixed baseline/no-baseline case.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Autoscaler treats Prometheus COUNTER metrics as instantaneous values instead of cumulative

5 participants