Skip to content

Commit c33d3c2

Browse files
committed
samples(datastore): Add client-side metrics sample
Add DatastoreMetricsSample demonstrating default built-in metrics with no configuration required. The sample runs a transaction flow (put, read-modify-write, delete) that exercises transaction_latency and transaction_attempt_count metrics, then prints instructions for verifying the data in Cloud Monitoring Metrics Explorer. Includes DatastoreMetricsSampleIT which runs against a real GCP project (GOOGLE_CLOUD_PROJECT env var required) and asserts the expected console output from the sample.
1 parent 816fa82 commit c33d3c2

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.datastore;
18+
19+
// [START datastore_client_side_metrics]
20+
import com.google.cloud.datastore.Datastore;
21+
import com.google.cloud.datastore.DatastoreOpenTelemetryOptions;
22+
import com.google.cloud.datastore.DatastoreOptions;
23+
import com.google.cloud.datastore.Entity;
24+
import com.google.cloud.datastore.Key;
25+
import com.google.cloud.datastore.Transaction;
26+
27+
import java.util.UUID;
28+
29+
/**
30+
* Demonstrates default client-side metrics for the Datastore Java client library using a
31+
* transaction flow.
32+
*
33+
* <p>Usage: DatastoreMetricsSample <projectId> <databaseId>
34+
*
35+
* <p>Client-side metrics are automatically exported to Google Cloud Monitoring under the {@code
36+
* custom.googleapis.com/internal/client} metric prefix, using the {@code
37+
* firestore.googleapis.com/Database} monitored resource. The {@code service} metric label is set to
38+
* {@code datastore.googleapis.com} to distinguish Datastore traffic from Firestore.
39+
*
40+
* <p>No configuration is required to enable metrics — they are on by default. To disable them, set
41+
* {@link DatastoreOpenTelemetryOptions#setExportBuiltinMetricsToGoogleCloudMonitoring(boolean)} to
42+
* {@code false}, or set the environment variable {@code DATASTORE_ENABLE_METRICS=false}.
43+
*
44+
* <p>Metrics recorded by this sample:
45+
*
46+
* <ul>
47+
* <li>{@code transaction_latency} — end-to-end latency of the transaction including retries.
48+
* <li>{@code transaction_attempt_count} — number of commit attempts for the transaction.
49+
* </ul>
50+
*
51+
* <p>To verify metrics in Cloud Monitoring after running this sample, navigate to:
52+
* <b>Cloud Console → Monitoring → Metrics Explorer</b> and filter by:
53+
*
54+
* <pre>
55+
* Metric : custom.googleapis.com/internal/client/transaction_latency
56+
* Resource: firestore.googleapis.com/Database
57+
* Label : service = datastore.googleapis.com
58+
* </pre>
59+
*/
60+
public class DatastoreMetricsSample {
61+
62+
public static void main(String[] args) throws Exception {
63+
if (args.length != 2) {
64+
System.err.println("Usage: DatastoreMetricsSample <projectId> <databaseId>");
65+
System.exit(1);
66+
}
67+
String projectId = args[0];
68+
String databaseId = args[1];
69+
String kind = "Kind-" + UUID.randomUUID().toString().substring(0, 8);
70+
71+
runSample(projectId, databaseId, kind);
72+
}
73+
74+
static void runSample(String projectId, String databaseId, String kind) throws Exception {
75+
// [START datastore_client_side_metrics_default]
76+
// Built-in metrics are exported to Cloud Monitoring by default.
77+
// No additional configuration is required.
78+
DatastoreOptions options =
79+
DatastoreOptions.newBuilder().setProjectId(projectId).setDatabaseId(databaseId).build();
80+
// [END datastore_client_side_metrics_default]
81+
82+
try (Datastore datastore = options.getService()) {
83+
System.out.printf(
84+
"Connected to project=%s database=%s%n", projectId, databaseId);
85+
System.out.println(
86+
"Built-in metrics are enabled by default and will be exported to"
87+
+ " Google Cloud Monitoring under custom.googleapis.com/internal/client/*");
88+
89+
runTransactionFlow(datastore, kind);
90+
91+
// The periodic metric reader flushes accumulated metrics to Cloud Monitoring on a fixed
92+
// interval (default: 60 seconds). A final flush also runs when the JVM shuts down via the
93+
// registered shutdown hook.
94+
System.out.println(
95+
"\nTransaction flow complete. Metrics will be flushed to Cloud Monitoring.");
96+
System.out.println(
97+
"Check Cloud Monitoring > Metrics Explorer for:"
98+
+ " custom.googleapis.com/internal/client/transaction_latency");
99+
100+
// Give some time for the periodic metric reader to flush metrics to Cloud Monitoring.
101+
System.out.println("Waiting 5 seconds for metrics to flush...");
102+
Thread.sleep(5000);
103+
}
104+
}
105+
106+
/**
107+
* Runs a full transaction flow: writes an entity, reads it back within a transaction, updates it,
108+
* then deletes it. This exercises the read-modify-write pattern that records both {@code
109+
* transaction_latency} and {@code transaction_attempt_count} metrics.
110+
*/
111+
static void runTransactionFlow(Datastore datastore, String kind) {
112+
Key key = datastore.newKeyFactory().setKind(kind).newKey("metrics-sample-entity");
113+
114+
// Step 1: Insert the entity outside any transaction to establish a baseline.
115+
Entity initial = Entity.newBuilder(key).set("status", "created").set("value", 0L).build();
116+
datastore.put(initial);
117+
System.out.printf("Inserted entity: kind=%s key=%s%n", kind, key.getName());
118+
119+
// Step 2: Read-modify-write inside a transaction.
120+
// This is the core pattern that generates transaction_latency and
121+
// transaction_attempt_count metrics.
122+
Entity updated =
123+
datastore.runInTransaction(
124+
transaction -> {
125+
Entity current = transaction.get(key);
126+
Entity modified =
127+
Entity.newBuilder(current)
128+
.set("status", "updated")
129+
.set("value", current.getLong("value") + 1)
130+
.build();
131+
transaction.put(modified);
132+
return modified;
133+
});
134+
System.out.printf(
135+
"Transaction committed: status=%s value=%d%n",
136+
updated.getString("status"), updated.getLong("value"));
137+
System.out.println(
138+
" → transaction_latency and transaction_attempt_count metrics recorded.");
139+
140+
// Step 3: Clean up.
141+
datastore.delete(key);
142+
System.out.printf("Deleted entity: kind=%s key=%s%n", kind, key.getName());
143+
}
144+
}
145+
// [END datastore_client_side_metrics]
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.datastore;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
21+
import com.google.cloud.datastore.Datastore;
22+
import com.google.cloud.datastore.DatastoreOptions;
23+
import com.google.cloud.datastore.Key;
24+
import com.rule.SystemsOutRule;
25+
import org.junit.After;
26+
import org.junit.Before;
27+
import org.junit.Rule;
28+
import org.junit.Test;
29+
import org.junit.runner.RunWith;
30+
import org.junit.runners.JUnit4;
31+
32+
/**
33+
* Integration test for {@link DatastoreMetricsSample}.
34+
*
35+
* <p>Requires a real GCP project with valid Application Default Credentials. Set the following
36+
* environment variables before running:
37+
*
38+
* <ul>
39+
* <li>{@code GOOGLE_CLOUD_PROJECT} — GCP project ID
40+
* <li>{@code DATASTORE_DATABASE_ID} — Datastore database ID (defaults to {@code ""} for the
41+
* default database)
42+
* </ul>
43+
*
44+
* <p>To verify that metrics appeared in Cloud Monitoring after this test runs, navigate to:
45+
*
46+
* <pre>
47+
* Cloud Console → Monitoring → Metrics Explorer
48+
* Metric : custom.googleapis.com/internal/client/transaction_latency
49+
* Resource: firestore.googleapis.com/Database
50+
* Label : service = datastore.googleapis.com
51+
* </pre>
52+
*/
53+
@RunWith(JUnit4.class)
54+
@SuppressWarnings("checkstyle:abbreviationaswordinname")
55+
public class DatastoreMetricsSampleIT {
56+
57+
private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT");
58+
private static final String DATABASE_ID =
59+
System.getenv().getOrDefault("DATASTORE_DATABASE_ID", "");
60+
61+
@Rule public final SystemsOutRule systemsOutRule = new SystemsOutRule();
62+
63+
private Datastore datastore;
64+
private String kind;
65+
66+
@Before
67+
public void setUp() {
68+
kind = "Kind-" + java.util.UUID.randomUUID().toString().substring(0, 8);
69+
datastore =
70+
DatastoreOptions.newBuilder()
71+
.setProjectId(PROJECT_ID)
72+
.setDatabaseId(DATABASE_ID)
73+
.build()
74+
.getService();
75+
cleanUp();
76+
}
77+
78+
@After
79+
public void tearDown() {
80+
cleanUp();
81+
System.setOut(null);
82+
}
83+
84+
@Test
85+
public void testTransactionFlowRecordsMetrics() throws Exception {
86+
DatastoreMetricsSample.runSample(PROJECT_ID, DATABASE_ID, kind);
87+
88+
systemsOutRule.assertContains("Built-in metrics are enabled by default");
89+
systemsOutRule.assertContains("Inserted entity");
90+
systemsOutRule.assertContains("Transaction committed");
91+
systemsOutRule.assertContains("transaction_latency and transaction_attempt_count metrics recorded");
92+
systemsOutRule.assertContains("Deleted entity");
93+
systemsOutRule.assertContains("Metrics will be flushed to Cloud Monitoring");
94+
}
95+
96+
@Test
97+
public void testRunTransactionFlow_updatesEntityCorrectly() {
98+
DatastoreMetricsSample.runTransactionFlow(datastore, kind);
99+
100+
// Entity is deleted at the end of the flow; confirm it no longer exists.
101+
Key key = datastore.newKeyFactory().setKind(kind).newKey("metrics-sample-entity");
102+
assertThat(datastore.get(key)).isNull();
103+
}
104+
105+
private void cleanUp() {
106+
Key key = datastore.newKeyFactory().setKind(kind).newKey("metrics-sample-entity");
107+
datastore.delete(key);
108+
}
109+
}

0 commit comments

Comments
 (0)