Cost Engineering
GCP
The Frugal Approach to GCP Cloud Monitoring Metrics Costs
Craig Conboy

With usage-billed observability services like Cloud Monitoring, cost optimization starts with your code. It's not just that applications emit the metrics; applications spend. Every sample written, every label added, every scrape interval chosen contributes to your bill.

The mechanics that matter: Cloud Monitoring bills chargeable metrics—custom, agent, workload, and external metrics—by volume ingested, at ~$0.26/MiB for the first tier (cheaper as volume grows). Google's own platform metrics are free. Volume is samples × bytes per sample, which makes this bill different from per-timeseries pricing: sampling frequency and label cardinality both multiply it, directly. Write at 10-second intervals instead of 60 and the same instrumentation costs 6x. Prometheus metrics through Managed Service for Prometheus bill per sample (~$0.06 per million at typical intervals)—same levers, same math.

Here's a practical walk-through of what to look for and how to optimize Cloud Monitoring costs at the code and configuration level.

Cost Trap Efficiency Pattern
Custom metrics duplicating free platform metrics Use the metrics GCP already publishes
Over-frequent sampling Write at 60s; reserve finer grain for what needs it
High-cardinality labels Label with bounded sets; never IDs
The agent collecting everything Configure the Ops Agent to collect what you chart
Unfiltered Prometheus scraping Filter targets and metrics before they ingest
Log-based metrics with unbounded labels Extract bounded labels or none
Point-at-a-time API writes Batch timeSeries.create calls
Chatty dashboards and pollers Tune refresh rates and query scope on readers

Attribute the Costs

Start with your bill. Cloud Monitoring's own tooling does the attribution: the Metrics Management page ranks metric names by billable ingestion volume and—usefully—shows which metrics are actually used in dashboards and alerting policies versus ingesting in silence. The distribution is reliably lopsided: a handful of metric names, usually agent or Prometheus metrics with rich labels and eager sample rates, account for most of the volume.

Then attribute those names to their source. An application writing custom metrics, an Ops Agent config collecting per-process stats on a big fleet, a Prometheus sidecar scraping everything a client library exports—each is a different fix. Which metric ingests gigabytes and appears on zero dashboards? Which label explodes the series count? Which scrape interval was copied from a tutorial? This granular attribution reveals which of the 8 efficiency patterns below deliver the highest impact.


Tackling Ingestion Volume Costs

Volume = series count × sample rate × retention of habit. All three are in your control.

Use the free platform metrics first

Cost impact: Ingestion volume

GCP publishes free metrics for Compute Engine, Cloud SQL, GKE, Cloud Run, Pub/Sub, and nearly everything else—CPU, memory, request counts, latencies. Writing your own copy pays chargeable-metric rates for data already on the shelf:

# Bad: hand-rolled CPU metric (billed per MiB ingested)
series = monitoring_v3.TimeSeries()
series.metric.type = 'custom.googleapis.com/app/cpu_usage'
...

# Good: delete the code. compute.googleapis.com/instance/cpu/utilization is free.

Before instrumenting anything infrastructure-shaped, check the built-in metrics list for the service. Custom metrics are for what only your application knows—orders placed, jobs queued, cache hit rate.

Benefit: Deleted metrics cost nothing and were duplicates anyway.

Sample at 60 seconds unless something needs faster

Cost impact: Ingestion volume (linear multiplier)

Because billing is per byte ingested, the write interval is a direct price multiplier: the same gauge at 10-second intervals costs 6x its 60-second self, and at 1-second intervals, 60x. Almost nothing downstream justifies it—dashboards render minutes, most alerts evaluate over windows:

# Bad: a point every 5 seconds because the loop runs every 5 seconds
while True:
    write_time_series('custom.googleapis.com/app/queue_depth', depth())
    time.sleep(5)

# Good: aggregate locally, write once a minute
while True:
    samples.append(depth())
    if time.time() - last_write >= 60:
        write_time_series('custom.googleapis.com/app/queue_depth',
                          mean(samples))
        samples.clear(); last_write = time.time()

Reserve finer grain for the rare signal where sub-minute reaction is a real requirement, and let everything else ride at 60s.

Benefit: 6-60x reduction in ingestion for over-sampled metrics—the single biggest lever on this bill.

Label with bounded sets, never IDs

Cost impact: Ingestion volume (multiplicative)

Every distinct label-value combination is another active time series writing samples on every interval, so label cardinalities multiply volume. endpoint (30) × status_family (5) is 150 series—fine. Add user_id and the same metric writes a sample per user per minute:

# Bad: one series per user
series.metric.labels['user_id'] = user.id

# Good: bounded labels; IDs belong in logs and traces
series.metric.labels['endpoint'] = route_template
series.metric.labels['status_family'] = f'{code // 100}xx'

The infrastructure variant churns instead of explodes: labels carrying pod names or build numbers mint fresh series every rollout. The rule for both: if you can't enumerate a label's values in advance, it doesn't belong on a metric.

Benefit: Series count—and the volume it multiplies—tracks your architecture, not your user table or restart count.


Tackling Collection Costs

The metrics you never wrote a line of code for still land on the bill.

Configure the agent to collect what you chart

Cost impact: Ingestion volume (fleet-multiplied)

The Ops Agent's default posture is generous, and its per-process metrics are the classic surprise: CPU, memory, and I/O for every process on the host, ingested as chargeable agent metrics, multiplied by every VM in the fleet. A hundred busy hosts can ingest more in process metrics than the application does in everything else:

# /etc/google-cloud-ops-agent/config.yaml
# Good: host-level metrics only, no per-process detail
metrics:
  receivers:
    hostmetrics:
      type: hostmetrics
      collection_interval: 60s
  service:
    pipelines:
      default_pipeline:
        receivers: [hostmetrics]   # omit the processes receiver

Keep per-process collection for the handful of hosts where you actually debug at that grain; the fleet default should be host-level at 60s.

Benefit: Often the largest single cut on VM-heavy projects—agent volume drops 50-90%.

Filter Prometheus scraping before it ingests

Cost impact: Samples ingested

Client libraries export everything—runtime internals, per-handler histograms with a dozen buckets, build info—and an unfiltered scrape ships it all into Managed Service for Prometheus at per-sample rates. Two levers, both upstream of the bill: scrape interval (30-60s, not the tutorial's 10s) and metric filtering:

# PodMonitoring: scrape less, keep less
spec:
  endpoints:
    - port: metrics
      interval: 60s
      metricRelabeling:
        - sourceLabels: [__name__]
          regex: 'go_gc_.*|process_.*|.*_bucket'   # drop what nobody charts
          action: drop

Histogram buckets deserve special suspicion: a 12-bucket histogram is 14 series per label combination, and most dashboards read the p50/p95 of a handful of them.

Benefit: 50-90% fewer samples from typical unfiltered exporters.

Extract bounded labels in log-based metrics—or none

Cost impact: Ingestion volume

Log-based metrics are a great way to get a counter without touching code, and a great way to smuggle cardinality in through the side door: a label extracted from a log field like path or error_message mints a series per distinct value, forever. Extract labels only from fields with enumerable values (status class, service name), and when in doubt, count without labels—the log entries themselves still hold the detail for when you need it.

Benefit: Log-based metrics stay a cheap counter instead of becoming a cardinality bill.


Tackling API & Read Costs

Batch writes; tune readers

Cost impact: API calls

Two smaller meters worth keeping honest. On the write side, timeSeries.create accepts up to 200 series per call—a loop writing one series at a time is 200x the API traffic and latency for the same data:

# Bad: one API call per series
for s in all_series:
    client.create_time_series(name=project, time_series=[s])

# Good: one call per 200
for chunk in chunks(all_series, 200):
    client.create_time_series(name=project, time_series=chunk)

On the read side, API reads bill past the free tier (~$0.01 per 1,000 calls), which stays invisible until an external tool syncs every metric in the project or a TV dashboard refreshes 40 charts every 10 seconds. Scope third-party integrations to the metric prefixes they chart, and match dashboard refresh to the data's actual resolution—refreshing faster than the samples arrive buys nothing.

Benefit: Less API overhead on the way in; a read line item that stays a rounding error.


Closing Thoughts

Cost-effective Cloud Monitoring requires two steps. First, let observed costs guide what needs optimization. The Metrics Management page hands you the attribution: metric names ranked by ingestion volume, cross-referenced against whether anything actually reads them. Trace the heavy ones to their source—the over-sampled writer, the unbounded label, the agent collecting per-process stats fleet-wide, the unfiltered scrape. This granular attribution reveals which of the 8 efficiency patterns above matter most. Second, apply the optimizations that address your concentrations: delete duplicates of free platform metrics, drop to 60-second sampling (6-60x on affected metrics), bound every label, trim the agent config, filter Prometheus at the scrape, keep log-based metrics label-light, batch the writes, and tune the readers.

You don't lose observability—you lose bytes nobody was reading. Volume-based billing has a virtue: it makes waste proportional and visible. Instrument what only your application knows, sample it as often as anyone looks, and let the free platform metrics do the rest.

Looking for help with cost optimizations like these? Book a Demo or Explore the Sandbox to see Frugal in action. Frugal attributes GCP Cloud Monitoring metrics costs to your code, finds inefficient usage, provides Frugal Fixes that reduce your bill, and helps keep you out of cost traps for new and changing code.

Back to Top
background Image
Robo Image

Take Frugal for a live test drive

Explore how Frugal scans your code and cloud services to find waste, recommend optimizations, and generate ready-to-use fixes in a secure, read-only environment.
background Image
robo image