geeViz.eeAuth.monitoring

Earth Engine usage monitoring — Cloud Monitoring poller.

Reads the earthengine.googleapis.com/project/cpu/usage_time metric from Cloud Monitoring, aggregates at a caller-chosen bucket size (grouping_seconds, default 3600 = hourly), and returns per-workload_tag rows. Attribution is not this module’s concern — it returns the raw tag string; the caller joins it to whatever storage they maintain (tag → parts mapping table, users table, etc.) to recover human-readable identity.

Design constraint: reversible tag encoding within EE’s 63-char workload_tag limit turns out to be infeasible for realistic attribution tuples (see comments in geeViz/eeAuth/tags.py). Rather than half-solve the reversibility problem, this module cleanly stops at “here are the tags and their EECU consumption” — everything upstream (mint the tag from parts + store the mapping) and everything downstream (join back for display, feed a billing ledger) stays with the caller.

Typical wiring:

from geeViz.eeAuth.monitoring import EEUsageMonitor

monitor = EEUsageMonitor(project="my-gcp-project",
                          cost_per_eecu_hour=0.40)
rows = monitor.poll(start_time=..., end_time=...)
# rows: [{workload_tag, bucket_start, eecu_seconds,
#         eecu_hours, cost_usd}, ...]
for row in rows:
    parts = my_tag_store.lookup(row["workload_tag"])
    my_db.upsert_ee_bucket(**row, **parts)

Cost model: cost_per_eecu_hour × eecu_hours. Google’s public commercial rate is $0.40/EECU-hour; non-commercial projects bill at 0. The monitor is agnostic — pass whatever your billing agreement says.

Classes

EEUsageMonitor(project[, ...])

Cloud Monitoring poller for EE workload consumption.

class geeViz.eeAuth.monitoring.EEUsageMonitor(project: str, cost_per_eecu_hour: float = 0.4, metric_type: str = 'earthengine.googleapis.com/project/cpu/usage_time')[source]

Bases: object

Cloud Monitoring poller for EE workload consumption.

One instance per GCP project. Thread-safe (the underlying MetricServiceClient is a shared singleton and gRPC channels are concurrent-safe). Cheap to construct — no I/O until poll().

Parameters:
  • project – GCP project id holding the EE metrics (the project billed for EE usage). Typically the GEE_PROJECT / GOOGLE_CLOUD_PROJECT your tenant runs against.

  • cost_per_eecu_hour – USD per EECU-hour applied to every returned row. Default $0.40 (Google commercial rate). Set to 0 for noncommercial projects.

  • metric_type – full Cloud Monitoring metric path. Default is the current EE CPU metric; override if Google renames it or you want to poll a different measurement.

DEFAULT_METRIC = 'earthengine.googleapis.com/project/cpu/usage_time'
poll(start_time: datetime, end_time: datetime | None = None, grouping_seconds: int = 3600, snap_to: bool = True) list[dict][source]

Query Cloud Monitoring for the configured metric between start_time and end_time (defaults to NOW), grouped by metric.workload_tag at grouping_seconds granularity.

Parameters:
  • start_time – timezone-aware datetime — lower bound.

  • end_time – timezone-aware datetime — upper bound. Defaults to now(UTC) if omitted.

  • grouping_seconds – bucket size in seconds. Common values: 60 (minute), 3600 (hour, default), 86400 (day), 604800 (week). Any positive int is accepted.

  • snap_to – if True (default), start_time is floored and end_time is ceiled to the nearest multiple of grouping_seconds from the Unix epoch. This keeps bucket boundaries stable across repeated polls (e.g. two pulls of the same window return the same rows) — highly recommended. Pass False to query the raw sub-bucket window (useful for ad-hoc slicing).

Returns one dict per (workload_tag, bucket_start):

{
    "workload_tag":  "wl_abc123...",
    "bucket_start":  datetime(2026, 8, 12, 14, 0, tzinfo=UTC),
    "eecu_seconds":  12.34,
    "eecu_hours":    0.003428,
    "cost_usd":      0.001371,
}

Rows with negative or missing values are floored to 0. Untagged points are skipped — attribution is impossible without a tag.

Returns an empty list on any Cloud Monitoring error (the error is logged). Callers should treat “no rows” as “no usage in window” rather than a hard failure.