Skip to content

Support count/aggregation of list elements in Custom Resource State metrics #3035

Description

@alikhil

What would you like to be added:

An aggregation/count capability for Custom Resource State (CRS) metrics that can count the elements of a list-valued field, optionally grouped by the value of a sub-field, and emit the result as a single aggregated Gauge — instead of emitting one time series per list element.

Concretely, given a CR with a status array like ArgoCD's Application.status.resources[]:

status:
  resources:
    - kind: Deployment
      name: api
      status: OutOfSync
      health: {status: Degraded}
    - kind: Service
      name: api
      status: Synced
      health: {status: Healthy}
    # ... hundreds more

...I'd like CRS to emit, per CR, the count of elements grouped by a chosen field value:

custom_resource_argocd_app_resource_count{name="my-app", sync_status="OutOfSync"} 1
custom_resource_argocd_app_resource_count{name="my-app", sync_status="Synced"}    1

Why is this needed:

Today CRS can only emit one series per list element (a path that resolves to a list is expanded element-by-element). There is no way to count or aggregate elements. To answer a simple question like "how many resources in this Application are OutOfSync?", the only option is to:

  1. Materialize an intermediate metric with one time series per list element — for Application.status.resources[] that is one series for every Kubernetes object managed by every Application (element name is effectively unbounded), across every cluster; then
  2. Aggregate it back down in PromQL (count by (name) (... {sync_status="OutOfSync"})); and
  3. Add recording + drop rules to discard the raw high-cardinality series so it isn't stored long-term.

So a small, bounded, per-CR number requires generating and scraping tens to hundreds of thousands of high-churn series just to throw most of them away. The counting could instead happen inside KSM, where the list is already being walked, at near-zero marginal cardinality (bounded by the distinct values of the group-by field). This pattern is common well beyond ArgoCD — counting status.conditions[] by type/status, counting connector tasks[] by state, counting pods/replicas in a custom controller's status, etc.

Describe the solution you'd like:

An optional aggregation on the Gauge type when path resolves to a list: count the elements, and (optionally) produce one series per distinct value of a grouped sub-field. Rough shape:

metrics:
  - name: "argocd_app_resource_count"
    help: "Number of managed resources, by sync status"
    each:
      type: Gauge
      gauge:
        path: [status, resources]     # resolves to a list
        aggregate: count              # NEW: emit count of elements instead of one metric per element
        groupBy:                      # NEW (optional): one series per distinct value, count per group
          sync_status: [status]       # sub-field, relative to each element
    labelsFromPath:
      name: [metadata, name]

Result:

custom_resource_argocd_app_resource_count{name="my-app", sync_status="OutOfSync"} <n>
custom_resource_argocd_app_resource_count{name="my-app", sync_status="Synced"}    <m>

Design notes / open questions:

  • Minimum viable version: just aggregate: count (no groupBy) to emit the total element count of a list — already useful, and the simplest to implement.
  • Grouping could reuse the existing labelsFromPath element-relative selector semantics; multiple group-by keys → one series per distinct combination.
  • Backward compatible: default behavior (no aggregate) is unchanged — still one metric per element.
  • An alternative API would be a dedicated each.type (e.g. Count) rather than an option on Gauge; whichever fits the maintainers' preferred model. A future filter/predicate (count only elements matching a condition) would be a natural follow-up but isn't required for the core use case, since groupBy already yields the per-status counts.

Additional context:

  • Motivating example is exposing per-ArgoCD-Application counts of OutOfSync (and unhealthy) managed resources for drift dashboards/alerting. ArgoCD's native metrics only report app-level sync status (binary), not a per-resource count, so KSM CRS is the natural place to derive it — but the current per-element-only model makes it expensive.
  • The relevant field is argoproj.io/Application .status.resources[], where each entry carries status (Synced/OutOfSync/Unknown) and health.status.
  • Related issue.

If this would be accepted as valuable, I'm happy to contribute the implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/featureCategorizes issue or PR as related to a new feature.needs-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.

    Type

    No type

    Projects

    Status
    Needs Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions