Skip to content

Commit b9f269f

Browse files
committed
Merge branch 'datastore-csm-impl-3' into datastore-csm-impl-2
2 parents 316cdbe + 701b10c commit b9f269f

3 files changed

Lines changed: 262 additions & 0 deletions

File tree

java-datastore/samples/snippets/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<dependency>
4242
<groupId>com.google.cloud</groupId>
4343
<artifactId>google-cloud-datastore</artifactId>
44+
<version>2.40.0</version>
4445
</dependency>
4546
<!-- [END datastore_install_with_bom] -->
4647

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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>Built-in metrics are currently disabled by default until the Cloud Monitoring namespace is
41+
* fully deployed. To enable them, set {@link
42+
* DatastoreOpenTelemetryOptions.Builder#setExportBuiltinMetricsToGoogleCloudMonitoring(boolean)} to
43+
* {@code true}, or set the environment variable {@code DATASTORE_ENABLE_METRICS=true}.
44+
*
45+
* <p>Metrics recorded by this sample:
46+
*
47+
* <ul>
48+
* <li>{@code transaction_latency} — end-to-end latency of the transaction including retries.
49+
* <li>{@code transaction_attempt_count} — number of commit attempts for the transaction.
50+
* </ul>
51+
*
52+
* <p>To verify metrics in Cloud Monitoring after running this sample, navigate to:
53+
* <b>Cloud Console → Monitoring → Metrics Explorer</b> and filter by:
54+
*
55+
* <pre>
56+
* Metric : custom.googleapis.com/internal/client/transaction_latency
57+
* Resource: firestore.googleapis.com/Database
58+
* Label : service = datastore.googleapis.com
59+
* </pre>
60+
*/
61+
public class DatastoreMetricsSample {
62+
63+
public static void main(String[] args) throws Exception {
64+
if (args.length != 2) {
65+
System.err.println("Usage: DatastoreMetricsSample <projectId> <databaseId>");
66+
System.exit(1);
67+
}
68+
String projectId = args[0];
69+
String databaseId = args[1];
70+
String kind = "Kind-" + UUID.randomUUID().toString().substring(0, 8);
71+
72+
runSample(projectId, databaseId, kind);
73+
}
74+
75+
static void runSample(String projectId, String databaseId, String kind) throws Exception {
76+
// [START datastore_client_side_metrics_default]
77+
// Built-in metrics are disabled by default. Enable them explicitly.
78+
DatastoreOptions options =
79+
DatastoreOptions.newBuilder()
80+
.setProjectId(projectId)
81+
.setDatabaseId(databaseId)
82+
.setOpenTelemetryOptions(
83+
DatastoreOpenTelemetryOptions.newBuilder()
84+
.setExportBuiltinMetricsToGoogleCloudMonitoring(true)
85+
.build())
86+
.build();
87+
// [END datastore_client_side_metrics_default]
88+
89+
try (Datastore datastore = options.getService()) {
90+
System.out.printf(
91+
"Connected to project=%s database=%s%n", projectId, databaseId);
92+
System.out.println(
93+
"Built-in metrics are explicitly enabled and will be exported to"
94+
+ " Google Cloud Monitoring under custom.googleapis.com/internal/client/*");
95+
96+
runTransactionFlow(datastore, kind);
97+
98+
// The periodic metric reader flushes accumulated metrics to Cloud Monitoring on a fixed
99+
// interval (default: 60 seconds). A final flush also runs when the JVM shuts down via the
100+
// registered shutdown hook.
101+
System.out.println(
102+
"\nTransaction flow complete. Metrics will be flushed to Cloud Monitoring.");
103+
System.out.println(
104+
"Check Cloud Monitoring > Metrics Explorer for:"
105+
+ " custom.googleapis.com/internal/client/transaction_latency");
106+
107+
// Give some time for the periodic metric reader to flush metrics to Cloud Monitoring.
108+
System.out.println("Waiting 5 seconds for metrics to flush...");
109+
Thread.sleep(5000);
110+
}
111+
}
112+
113+
/**
114+
* Runs a full transaction flow: writes an entity, reads it back within a transaction, updates it,
115+
* then deletes it. This exercises the read-modify-write pattern that records both {@code
116+
* transaction_latency} and {@code transaction_attempt_count} metrics.
117+
*/
118+
static void runTransactionFlow(Datastore datastore, String kind) {
119+
Key key = datastore.newKeyFactory().setKind(kind).newKey("metrics-sample-entity");
120+
121+
// Step 1: Insert the entity outside any transaction to establish a baseline.
122+
Entity initial = Entity.newBuilder(key).set("status", "created").set("value", 0L).build();
123+
datastore.put(initial);
124+
System.out.printf("Inserted entity: kind=%s key=%s%n", kind, key.getName());
125+
126+
// Step 2: Read-modify-write inside a transaction.
127+
// This is the core pattern that generates transaction_latency and
128+
// transaction_attempt_count metrics.
129+
Entity updated =
130+
datastore.runInTransaction(
131+
transaction -> {
132+
Entity current = transaction.get(key);
133+
Entity modified =
134+
Entity.newBuilder(current)
135+
.set("status", "updated")
136+
.set("value", current.getLong("value") + 1)
137+
.build();
138+
transaction.put(modified);
139+
return modified;
140+
});
141+
System.out.printf(
142+
"Transaction committed: status=%s value=%d%n",
143+
updated.getString("status"), updated.getLong("value"));
144+
System.out.println(
145+
" → transaction_latency and transaction_attempt_count metrics recorded.");
146+
147+
// Step 3: Clean up.
148+
datastore.delete(key);
149+
System.out.printf("Deleted entity: kind=%s key=%s%n", kind, key.getName());
150+
}
151+
}
152+
// [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 explicitly enabled");
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)