Skip to content

Commit 461e413

Browse files
chore: merge main into generate-libraries-main
2 parents 1c7aaf8 + 4920266 commit 461e413

File tree

7 files changed

+493
-2
lines changed

7 files changed

+493
-2
lines changed

google-cloud-spanner/pom.xml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,12 @@
276276
<artifactId>proto-google-cloud-monitoring-v3</artifactId>
277277
<version>3.76.0</version>
278278
</dependency>
279+
<dependency>
280+
<groupId>com.google.api.grpc</groupId>
281+
<artifactId>grpc-google-cloud-monitoring-v3</artifactId>
282+
<version>3.63.0</version>
283+
<scope>test</scope>
284+
</dependency>
279285
<dependency>
280286
<groupId>com.google.auth</groupId>
281287
<artifactId>google-auth-library-oauth2-http</artifactId>
@@ -522,7 +528,11 @@
522528
<argument>-classpath</argument>
523529
<classpath/>
524530
<argument>org.openjdk.jmh.Main</argument>
525-
<argument>${benchmark.name}</argument>
531+
<argument>${benchmark.name}</argument>
532+
<argument>-rf</argument>
533+
<argument>JSON</argument>
534+
<argument>-rff</argument>
535+
<argument>jmh-results.json</argument>
526536
</arguments>
527537
</configuration>
528538
</execution>
@@ -544,6 +554,21 @@
544554
</plugins>
545555
</build>
546556
</profile>
557+
<profile>
558+
<id>validate-benchmark</id>
559+
<build>
560+
<plugins>
561+
<plugin>
562+
<groupId>org.codehaus.mojo</groupId>
563+
<artifactId>exec-maven-plugin</artifactId>
564+
<configuration>
565+
<mainClass>com.google.cloud.spanner.benchmarking.BenchmarkValidator</mainClass>
566+
<classpathScope>test</classpathScope>
567+
</configuration>
568+
</plugin>
569+
</plugins>
570+
</build>
571+
</profile>
547572
<profile>
548573
<id>slow-tests</id>
549574
<build>

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
import com.google.api.gax.core.CredentialsProvider;
2323
import com.google.api.gax.core.FixedCredentialsProvider;
2424
import com.google.api.gax.core.NoCredentialsProvider;
25+
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
2526
import com.google.api.gax.rpc.PermissionDeniedException;
2627
import com.google.auth.Credentials;
28+
import com.google.cloud.NoCredentials;
2729
import com.google.cloud.monitoring.v3.MetricServiceClient;
2830
import com.google.cloud.monitoring.v3.MetricServiceSettings;
2931
import com.google.common.annotations.VisibleForTesting;
@@ -34,6 +36,7 @@
3436
import com.google.monitoring.v3.ProjectName;
3537
import com.google.monitoring.v3.TimeSeries;
3638
import com.google.protobuf.Empty;
39+
import io.grpc.ManagedChannelBuilder;
3740
import io.opentelemetry.sdk.common.CompletableResultCode;
3841
import io.opentelemetry.sdk.metrics.InstrumentType;
3942
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
@@ -79,7 +82,7 @@ static SpannerCloudMonitoringExporter create(
7982
throws IOException {
8083
MetricServiceSettings.Builder settingsBuilder = MetricServiceSettings.newBuilder();
8184
CredentialsProvider credentialsProvider;
82-
if (credentials == null) {
85+
if (credentials == null || credentials instanceof NoCredentials) {
8386
credentialsProvider = NoCredentialsProvider.create();
8487
} else {
8588
credentialsProvider = FixedCredentialsProvider.create(credentials);
@@ -92,6 +95,19 @@ static SpannerCloudMonitoringExporter create(
9295
settingsBuilder.setUniverseDomain(universeDomain);
9396
}
9497

98+
if (System.getProperty("jmh.monitoring-server-port") != null) {
99+
settingsBuilder.setTransportChannelProvider(
100+
InstantiatingGrpcChannelProvider.newBuilder()
101+
.setCredentials(NoCredentials.getInstance())
102+
.setChannelConfigurator(
103+
managedChannelBuilder ->
104+
ManagedChannelBuilder.forAddress(
105+
"0.0.0.0",
106+
Integer.parseInt(System.getProperty("jmh.monitoring-server-port")))
107+
.usePlaintext())
108+
.build());
109+
}
110+
95111
Duration timeout = Duration.ofMinutes(1);
96112
// TODO: createServiceTimeSeries needs special handling if the request failed. Leaving
97113
// it as not retried for now.

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,6 +2013,11 @@ public CallCredentialsProvider getCallCredentialsProvider() {
20132013
}
20142014

20152015
private boolean usesNoCredentials() {
2016+
// When JMH is enabled, we need to enable built-in metrics
2017+
if (System.getProperty("jmh.enabled") != null
2018+
&& System.getProperty("jmh.enabled").equals("true")) {
2019+
return false;
2020+
}
20162021
return Objects.equals(getCredentials(), NoCredentials.getInstance());
20172022
}
20182023

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright 2025 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+
* https://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.google.cloud.spanner.benchmarking;
18+
19+
import com.google.cloud.spanner.benchmarking.BenchmarkValidator.BaselineResult.BenchmarkResult;
20+
import com.google.cloud.spanner.benchmarking.BenchmarkValidator.BaselineResult.BenchmarkResult.Percentile;
21+
import com.google.gson.Gson;
22+
import com.google.gson.reflect.TypeToken;
23+
import java.io.File;
24+
import java.io.IOException;
25+
import java.net.URL;
26+
import java.nio.file.Files;
27+
import java.nio.file.Paths;
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
import java.util.Map;
31+
32+
public class BenchmarkValidator {
33+
34+
private final BaselineResult expectedResults;
35+
private final List<ActualBenchmarkResult> actualResults;
36+
37+
public BenchmarkValidator(String baselineFile, String actualFile) {
38+
Gson gson = new Gson();
39+
// Load expected result JSON from resource folder
40+
this.expectedResults = gson.fromJson(loadJsonFromResources(baselineFile), BaselineResult.class);
41+
// Load the actual result from current benchmarking run
42+
this.actualResults =
43+
gson.fromJson(
44+
loadJsonFromFile(actualFile),
45+
new TypeToken<ArrayList<ActualBenchmarkResult>>() {}.getType());
46+
}
47+
48+
void validate() {
49+
// Validating the resultant percentile against expected percentile with allowed threshold
50+
for (ActualBenchmarkResult actualResult : actualResults) {
51+
BenchmarkResult expectResult = expectedResults.benchmarkResultMap.get(actualResult.benchmark);
52+
if (expectResult == null) {
53+
throw new ValidationException(
54+
"Missing expected benchmark configuration for actual benchmarking");
55+
}
56+
Map<String, Double> actualPercentilesMap = actualResult.primaryMetric.scorePercentiles;
57+
// We will only be comparing the percentiles(p50, p90, p90) which are configured in the
58+
// expected percentiles. This allows some checks to be disabled if required.
59+
for (Percentile expectedPercentile : expectResult.scorePercentiles) {
60+
String percentile = expectedPercentile.percentile;
61+
double difference =
62+
calculatePercentageDifference(
63+
expectedPercentile.baseline, actualPercentilesMap.get(percentile));
64+
// if an absolute different in percentage is greater than allowed difference
65+
// Then we are throwing validation error
66+
if (Math.abs(Math.ceil(difference)) > expectedPercentile.difference) {
67+
throw new ValidationException(
68+
String.format(
69+
"[%s][%s] Expected percentile %s[+/-%s] but got %s",
70+
actualResult.benchmark,
71+
percentile,
72+
expectedPercentile.baseline,
73+
expectedPercentile.difference,
74+
actualPercentilesMap.get(percentile)));
75+
}
76+
}
77+
}
78+
}
79+
80+
public static double calculatePercentageDifference(double base, double compareWith) {
81+
if (base == 0) {
82+
return 0.0;
83+
}
84+
return ((compareWith - base) / base) * 100;
85+
}
86+
87+
private String loadJsonFromFile(String file) {
88+
try {
89+
return new String(Files.readAllBytes(Paths.get(file)));
90+
} catch (IOException e) {
91+
throw new ValidationException("Failed to read file: " + file, e);
92+
}
93+
}
94+
95+
private String loadJsonFromResources(String baselineFile) {
96+
URL resourceUrl = getClass().getClassLoader().getResource(baselineFile);
97+
if (resourceUrl == null) {
98+
throw new ValidationException("File not found: " + baselineFile);
99+
}
100+
File file = new File(resourceUrl.getFile());
101+
return loadJsonFromFile(file.getAbsolutePath());
102+
}
103+
104+
static class ActualBenchmarkResult {
105+
String benchmark;
106+
PrimaryMetric primaryMetric;
107+
108+
static class PrimaryMetric {
109+
Map<String, Double> scorePercentiles;
110+
}
111+
}
112+
113+
static class BaselineResult {
114+
Map<String, BenchmarkResult> benchmarkResultMap;
115+
116+
static class BenchmarkResult {
117+
List<Percentile> scorePercentiles;
118+
119+
static class Percentile {
120+
String percentile;
121+
Double baseline;
122+
Double difference;
123+
}
124+
}
125+
}
126+
127+
static class ValidationException extends RuntimeException {
128+
ValidationException(String message) {
129+
super(message);
130+
}
131+
132+
ValidationException(String message, Throwable cause) {
133+
super(message, cause);
134+
}
135+
}
136+
137+
private static String parseCommandLineArgs(String[] args, String key) {
138+
if (args == null) {
139+
return "";
140+
}
141+
for (String arg : args) {
142+
if (arg.startsWith("--" + key)) {
143+
String[] splits = arg.split("=");
144+
if (splits.length == 2) {
145+
return splits[1].trim();
146+
}
147+
}
148+
}
149+
return "";
150+
}
151+
152+
public static void main(String[] args) {
153+
String actualFile = parseCommandLineArgs(args, "file");
154+
new BenchmarkValidator("com/google/cloud/spanner/jmh/jmh-baseline.json", actualFile).validate();
155+
}
156+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2025 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+
* https://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.google.cloud.spanner.benchmarking;
18+
19+
import com.google.monitoring.v3.CreateTimeSeriesRequest;
20+
import com.google.monitoring.v3.MetricServiceGrpc.MetricServiceImplBase;
21+
import com.google.protobuf.Empty;
22+
import io.grpc.Status;
23+
import io.grpc.stub.StreamObserver;
24+
25+
class MonitoringServiceImpl extends MetricServiceImplBase {
26+
27+
@Override
28+
public void createServiceTimeSeries(
29+
CreateTimeSeriesRequest request, StreamObserver<Empty> responseObserver) {
30+
try {
31+
Thread.sleep(100);
32+
responseObserver.onNext(Empty.getDefaultInstance());
33+
responseObserver.onCompleted();
34+
} catch (InterruptedException e) {
35+
responseObserver.onError(
36+
Status.CANCELLED.withCause(e).withDescription(e.getMessage()).asException());
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)