Cloud Monitoring
floci-gcp emulates Google Cloud Monitoring (the Metrics API) over gRPC and REST using the real
google.monitoring.v3.MetricService protocol. Define metric descriptors, write time series
data points, and read them back — useful for exercising custom-metric ingestion and queries without
a real Monitoring backend.
Configuration
| Variable | Default | Description |
|---|---|---|
FLOCI_GCP_SERVICES_MONITORING_ENABLED |
true |
Enable/disable Cloud Monitoring |
Endpoint
Cloud Monitoring has no *_EMULATOR_HOST convention. Point the client at floci-gcp by overriding
the API endpoint / transport channel and disabling credentials:
- gRPC (Java/Python/Go/Node): build the v3
MetricServiceClientwith a plaintext channel tolocalhost:4588and anonymous/no credentials (see Quick Start). - REST:
/v3/projects/{project}/...(metric descriptors, monitored resource descriptors, time series).
Scope
- Metric descriptors:
CreateMetricDescriptor(upsert),GetMetricDescriptor,ListMetricDescriptors,DeleteMetricDescriptor. - Monitored resource descriptors:
ListMonitoredResourceDescriptors,GetMonitoredResourceDescriptor. - Time series:
CreateTimeSeries(write points) andListTimeSeries(read back over a time interval, raw or aggregated). TypedValuepoint values (bool,int64,double,string,distribution) are preserved round-trip.- Pagination (
pageSize/pageToken/nextPageToken) on all list methods.ListMetricDescriptorsclamps the page size to 10,000;ListTimeSeriesclamps to 100,000, where under theFULLview the page size limits the total number of points (a series may span pages) and underHEADERSit limits the number of series.
Aggregation
ListTimeSeries supports a subset of the real API's alignment/reduction:
- Per-series aligners:
ALIGN_NONE,ALIGN_SUM,ALIGN_MEAN,ALIGN_MIN,ALIGN_MAX,ALIGN_COUNT,ALIGN_DELTA,ALIGN_RATE. Output kinds/types follow the proto:ALIGN_MEAN/ALIGN_RATE→DOUBLE,ALIGN_COUNT→INT64,ALIGN_DELTA→ kindDELTA,ALIGN_RATE→ kindGAUGE. - Cross-series reducers:
REDUCE_NONE,REDUCE_SUM,REDUCE_MEAN,REDUCE_MIN,REDUCE_MAX,REDUCE_COUNT, withgroup_by_fields(resource.type,resource.label.<key>,metric.label.<key>). alignment_periodmust be at least 60 seconds and is required whenever an aligner other thanALIGN_NONEis set; a reducer requires an aligner. Unsupported aligners/reducers are rejected withINVALID_ARGUMENTrather than silently ignored.- Alignment buckets are anchored to the request interval's end time: each aligned point's
endTimefalls oninterval.endTime - k * alignment_period. - Over REST, pass
aggregation.alignmentPeriod(e.g.60s),aggregation.perSeriesAligner,aggregation.crossSeriesReducerand repeatedaggregation.groupByFieldsquery parameters.
Validation semantics
Write-path rules matching the documented API behavior:
CreateTimeSeries: at most 200TimeSeriesper request; each series carries exactly one point; the point'sendTimemust be at most 25 hours in the past and 5 minutes in the future, and must be strictly newer than the most recent stored point of the same series (metric type + labels + monitored resource).GAUGEintervals are point-in-time (startTimeabsent or equal toendTime);DELTA/CUMULATIVEintervals requirestartTime < endTime.- Writing to a nonexistent metric auto-creates its descriptor: the kind must be
GAUGE(default) orCUMULATIVE, and the value type is inferred from the point (BOOL,INT64,DOUBLEorDISTRIBUTION). CreateMetricDescriptoris an upsert: re-creating an existing type updates it, but labels are unioned — existing label keys are never removed.BOOL/STRINGvalue types are only valid withGAUGE. The metric type must be domain-prefixed (e.g.custom.googleapis.com/...).DeleteMetricDescriptoronly accepts user-created metrics (custom.googleapis.com/orexternal.googleapis.com/prefixes).ListTimeSeriesrequires a filter naming a single metric type (metric.type = "...") and aninterval.endTime; the read interval is half-open(startTime, endTime].
Documented deviations from real GCP
CreateTimeSeriesis all-or-nothing: on any invalid series the whole request fails withINVALID_ARGUMENTand nothing is written (real GCP writes the valid subset and reports partial failures viaCreateTimeSeriesSummaryerror details).- Alignment emits points only for buckets that contain data — no interpolation of empty periods for
ALIGN_DELTA/ALIGN_RATE. DISTRIBUTIONandSTRINGvalues cannot be aggregated.- A missing read
interval.startTimeis treated as unbounded (real GCP defaults it to the end time). - A single
CreateTimeSeriesrequest may carry multiple chronologically ordered points for the same series acrossTimeSeriesentries (real GCP rejects duplicate series identities per request).
Quick Start
MetricServiceClient client = MetricServiceClient.create(
MetricServiceSettings.newBuilder()
.setTransportChannelProvider(
InstantiatingGrpcChannelProvider.newBuilder()
.setEndpoint("localhost:4588")
.setChannelConfigurator(b -> b.usePlaintext())
.build())
.setCredentialsProvider(NoCredentialsProvider.create())
.build());
ProjectName project = ProjectName.of("floci-local");
// Write a custom-metric data point
TimeSeries series = TimeSeries.newBuilder()
.setMetric(Metric.newBuilder().setType("custom.googleapis.com/my_metric").build())
.addPoints(Point.newBuilder()
.setInterval(TimeInterval.newBuilder().setEndTime(Timestamps.now()).build())
.setValue(TypedValue.newBuilder().setDoubleValue(42.0).build())
.build())
.build();
client.createTimeSeries(project, List.of(series));
// Read it back
client.listTimeSeries(project,
"metric.type=\"custom.googleapis.com/my_metric\"",
interval,
ListTimeSeriesRequest.TimeSeriesView.FULL);
Notes
- Custom metrics (
custom.googleapis.com/*) are the primary use case; metric and time-series data is held in the configured storage backend, namespaced by project ID. ListTimeSeriesreads back data over the requested time interval; combine with the metric/resource filter to scope results.