Notes / Projects / Platform Shipsolid / 07 Cost Governance

Metric Label Standards for Cost Attribution

**Goal:** Attribute Grafana Cloud metrics ingestion cost to business unit, product, and environment.

Updated May 1, 2026 · §202603301546 ·

Metric Label Standards for Cost Attribution

Goal: Attribute Grafana Cloud metrics ingestion cost to business unit, product, and environment. Infra vs app metric separation is handled via metric name prefix — no label required for that split.

1. Infra vs App Metric Separation (Common to All Approaches)

Kubernetes and Windows exporters expose well-known metric prefixes that identify infra metrics without any custom label. ACA and Azure PaaS metrics arrive via Azure Monitor — a separate data source — so they are already isolated at the source level.

Metric PrefixSourceClassification
kube_*kube-state-metricsAKS infra
node_*node-exporterAKS node (host)
container_*cAdvisorAKS container runtime
windows_*windows-exporterAKS Windows nodes
Everything elseApp instrumentation / OTelApp workload

Cost attribution split query (Grafana Cloud):

# Infra series count
count({__name__=~"kube_.+|node_.+|container_.+|windows_.+"})

# App series count — attributed by platform + BU + product + env
count by (deployment_environment) ({__name__!~"kube_.+|node_.+|container_.+|windows_.+"})

2. Approach A — 4-Segment Compound Label

A single label encodes platform, business unit, product, and environment.

Format:

<infra_type>-<business_unit>-<app>-<env>

Allowed values:

SegmentAllowed ValuesCount
infra_typeaks, aca2
business_unitdgeg, dgag, ieo3
appmdixai, passport, infra3
envdev, qa, prod, sandbox4

Max combinations: 2 × 3 × 3 × 4 = 72

Note on migration: infra_type reflects where the backend is currently deployed. Since only one platform emits metrics at a time, the label value changes once during a migration cutover (e.g., aca-dgeg-mdixai-prodaks-dgeg-mdixai-prod). Dashboards and alert rules must be updated at that point.

Examples:

deployment_environmentMeaning
aks-dgeg-mdixai-prodMDIxAI on AKS in DGEG, production
aca-dgeg-mdixai-devMDIxAI on ACA in DGEG, dev
aks-dgag-passport-prodPassport on AKS in DGAG, production
aca-dgag-passport-qaPassport on ACA in DGAG, QA
aks-ieo-infra-prodInfra product on AKS in IEO, production
aca-ieo-infra-sandboxInfra product on ACA in IEO, sandbox

Prometheus scrape config:

scrape_configs:
  - job_name: aks-dgeg-mdixai-prod
    static_configs:
      - targets: ["mdixai-api.internal:8080"]
        labels:
          deployment_environment: "aks-dgeg-mdixai-prod"

OTel Collector resource processor:

processors:
  resource:
    attributes:
      - key: deployment_environment
        value: "aks-dgeg-mdixai-prod"
        action: upsert

Grafana template variable:

$deployment_environment → label_values(up, deployment_environment)

Single dropdown, full context visible at a glance.

PromQL panel filter:

rate(http_requests_total{deployment_environment="$deployment_environment"}[5m])

Alert rule:

- alert: HighErrorRate
  expr: |
    rate(http_requests_total{status=~"5..", deployment_environment="aks-dgeg-mdixai-prod"}[5m])
    / rate(http_requests_total{deployment_environment="aks-dgeg-mdixai-prod"}[5m]) > 0.02
  labels:
    severity: critical
    deployment_environment: "{{ $labels.deployment_environment }}"

Validation regex:

^(aks|aca)-(dgeg|dgag|ieo)-(mdixai|passport|infra)-(dev|qa|prod|sandbox)$

3. Approach B — 3-Segment Compound Label

Platform dropped — a single label encodes business unit, product, and environment only. Platform is identifiable via metric name prefix or scrape job name.

Format:

<business_unit>-<app>-<env>

Allowed values:

SegmentAllowed ValuesCount
business_unitdgeg, dgag, ieo3
appmdixai, passport, infra3
envdev, qa, prod, sandbox4

Max combinations: 3 × 3 × 4 = 36

Examples:

deployment_environmentMeaning
dgeg-mdixai-prodMDIxAI in DGEG, production
dgeg-mdixai-devMDIxAI in DGEG, dev
dgag-passport-prodPassport in DGAG, production
dgag-passport-qaPassport in DGAG, QA
ieo-infra-prodInfra product in IEO, production
ieo-infra-sandboxInfra product in IEO, sandbox

Prometheus scrape config:

scrape_configs:
  - job_name: dgeg-mdixai-prod
    static_configs:
      - targets: ["mdixai-api.internal:8080"]
        labels:
          deployment_environment: "dgeg-mdixai-prod"

OTel Collector resource processor:

processors:
  resource:
    attributes:
      - key: deployment_environment
        value: "dgeg-mdixai-prod"
        action: upsert

Grafana template variable:

$deployment_environment → label_values(up, deployment_environment)

PromQL panel filter:

rate(http_requests_total{deployment_environment="$deployment_environment"}[5m])

Alert rule:

- alert: HighErrorRate
  expr: |
    rate(http_requests_total{status=~"5..", deployment_environment="dgeg-mdixai-prod"}[5m])
    / rate(http_requests_total{deployment_environment="dgeg-mdixai-prod"}[5m]) > 0.02
  labels:
    severity: critical
    deployment_environment: "{{ $labels.deployment_environment }}"

Validation regex:

^(dgeg|dgag|ieo)-(mdixai|passport|infra)-(dev|qa|prod|sandbox)$

4. Approach C — Flat Labels

Three independent labels, each carrying one attribution dimension. Platform identified via metric name prefix — no label needed.

Label set:

LabelAllowed ValuesCount
business_unitdgeg, dgag, ieo3
appmdixai, passport, infra3
envdev, qa, prod, sandbox4

Max combinations: 3 × 3 × 4 = 36 (identical to Approach B)

Prometheus scrape config:

scrape_configs:
  - job_name: dgeg-mdixai-prod
    static_configs:
      - targets: ["mdixai-api.internal:8080"]
        labels:
          business_unit: "dgeg"
          app: "mdixai"
          env: "prod"

OTel Collector resource processor:

processors:
  resource:
    attributes:
      - key: business_unit
        value: "dgeg"
        action: upsert
      - key: app
        value: "mdixai"
        action: upsert
      - key: env
        value: "prod"
        action: upsert

Grafana template variables — 3 independent dropdowns:

VariableTypeQuery
envCustomdev,qa,prod,sandbox
business_unitQuerylabel_values(up, business_unit)
appQuerylabel_values(up{business_unit="$business_unit"}, app)

PromQL panel filter:

rate(http_requests_total{business_unit="$business_unit", app="$app", env="$env"}[5m])

Alert rule:

- alert: HighErrorRate
  expr: |
    rate(http_requests_total{status=~"5..", business_unit="dgeg", app="mdixai", env="prod"}[5m])
    / rate(http_requests_total{business_unit="dgeg", app="mdixai", env="prod"}[5m]) > 0.02
  labels:
    severity: critical
    business_unit: "{{ $labels.business_unit }}"
    app: "{{ $labels.app }}"
    env: "{{ $labels.env }}"

Cost attribution query by BU:

count by (business_unit, app, env) (
  {__name__!~"kube_.+|node_.+|container_.+|windows_.+"}
)

5. Comparison & Decision

CriterionApproach A — 4-Segment CompoundApproach B — 3-Segment CompoundApproach C — Flat Labels
Label structureaks/aca-bu-app-envbu-app-envbusiness_unit + app + env
Max combinations723636
Platform visible in labelYesNoNo
AKS vs ACA cost splitNative — in label valueVia metric prefix queryVia metric prefix query
Migration impactLabel value changes on cutover — dashboards and alerts need one-time updateNo impactNo impact
PromQL exact filterExact match on one labelExact match on one labelExact match on three labels
PromQL partial filter (e.g. all prod)Regex: =~".*-prod"Regex: =~".*-prod"Exact: env="prod"
Grafana variable UXSingle dropdown — full contextSingle dropdownThree independent dropdowns
Alert notification readabilityOne field, platform + contextOne field, no platformThree fields, requires mental join
OTel semantic conventionsNon-standardNon-standardAligned natively
Grafana Adaptive MetricsOpaque stringOpaque stringOptimizes each dimension
Label enforcementOne regex ruleOne regex ruleThree validation rules
Extensibility (adding a dimension)Breaking changeBreaking changeAdditive
Auto-instrumentation compatibilityCustom processor requiredCustom processor requiredOTel SDKs emit natively

Deciding factors:

  • If platform visibility in the label and alert notifications matters → Approach A
  • If migration simplicity (zero label churn on cutover) is preferred → Approach B
  • If OTel compatibility, Adaptive Metrics, and PromQL ergonomics are the priority → Approach C

Decision: (to be confirmed)

Local graph

Full graph →