Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/update_generation_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
# the branch into which the pull request is merged
base_branch: main
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.CLOUD_JAVA_BOT_TOKEN }}
Expand Down
27 changes: 26 additions & 1 deletion google-cloud-spanner/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@
<artifactId>proto-google-cloud-monitoring-v3</artifactId>
<version>3.76.0</version>
</dependency>
<dependency>
<groupId>com.google.api.grpc</groupId>
<artifactId>grpc-google-cloud-monitoring-v3</artifactId>
<version>3.63.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
Expand Down Expand Up @@ -522,7 +528,11 @@
<argument>-classpath</argument>
<classpath/>
<argument>org.openjdk.jmh.Main</argument>
<argument>${benchmark.name}</argument>
<argument>${benchmark.name}</argument>
<argument>-rf</argument>
<argument>JSON</argument>
<argument>-rff</argument>
<argument>jmh-results.json</argument>
</arguments>
</configuration>
</execution>
Expand All @@ -544,6 +554,21 @@
</plugins>
</build>
</profile>
<profile>
<id>validate-benchmark</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>com.google.cloud.spanner.benchmarking.BenchmarkValidator</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>slow-tests</id>
<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.api.gax.core.CredentialsProvider;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.core.NoCredentialsProvider;
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
import com.google.api.gax.rpc.PermissionDeniedException;
import com.google.auth.Credentials;
import com.google.cloud.monitoring.v3.MetricServiceClient;
Expand All @@ -34,6 +35,7 @@
import com.google.monitoring.v3.ProjectName;
import com.google.monitoring.v3.TimeSeries;
import com.google.protobuf.Empty;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
Expand Down Expand Up @@ -92,6 +94,15 @@ static SpannerCloudMonitoringExporter create(
settingsBuilder.setUniverseDomain(universeDomain);
}

if (System.getProperty("jmh.monitoring-server") != null) {
settingsBuilder.setTransportChannelProvider(
InstantiatingGrpcChannelProvider.newBuilder()
.setChannelConfigurator(
managedChannelBuilder ->
InProcessChannelBuilder.forName(System.getProperty("jmh.monitoring-server")))
.build());
}

Duration timeout = Duration.ofMinutes(1);
// TODO: createServiceTimeSeries needs special handling if the request failed. Leaving
// it as not retried for now.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.spanner.benchmarking;

import com.google.cloud.spanner.benchmarking.BenchmarkValidator.BaselineResult.BenchmarkResult;
import com.google.cloud.spanner.benchmarking.BenchmarkValidator.BaselineResult.BenchmarkResult.Percentile;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class BenchmarkValidator {

private final BaselineResult expectedResults;
private final List<ActualBenchmarkResult> actualResults;

public BenchmarkValidator(String baselineFile, String actualFile) {
Gson gson = new Gson();
// Load expected result JSON from resource folder
this.expectedResults = gson.fromJson(loadJsonFromResources(baselineFile), BaselineResult.class);
// Load the actual result from current benchmarking run
this.actualResults =
gson.fromJson(
loadJsonFromFile(actualFile),
new TypeToken<ArrayList<ActualBenchmarkResult>>() {}.getType());
}

void validate() {
// Validating the resultant percentile against expected percentile with allowed threshold
for (ActualBenchmarkResult actualResult : actualResults) {
BenchmarkResult expectResult = expectedResults.benchmarkResultMap.get(actualResult.benchmark);
if (expectResult == null) {
throw new ValidationException(
"Missing expected benchmark configuration for actual benchmarking");
}
Map<String, Double> actualPercentilesMap = actualResult.primaryMetric.scorePercentiles;
// We will only be comparing the percentiles(p50, p90, p90) which are configured in the
// expected
// percentiles. This allows some checks to be disabled if required.
for (Percentile expectedPercentile : expectResult.scorePercentiles) {
String percentile = expectedPercentile.percentile;
double difference =
calculatePercentageDifference(
expectedPercentile.baseline, actualPercentilesMap.get(percentile));
// if an absolute different in percentage is greater than allowed difference
// Then we are throwing validation error
if (Math.abs(Math.ceil(difference)) > expectedPercentile.difference) {
throw new ValidationException(
String.format(
"[%s][%s] Expected percentile %s[+/-%s] but got %s",
actualResult.benchmark,
percentile,
expectedPercentile.baseline,
expectedPercentile.difference,
actualPercentilesMap.get(percentile)));
}
}
}
}

public static double calculatePercentageDifference(double base, double compareWith) {
if (base == 0) {
return 0.0;
}
return ((compareWith - base) / base) * 100;
}

private String loadJsonFromFile(String file) {
try {
return new String(Files.readAllBytes(Paths.get(file)));
} catch (IOException e) {
throw new ValidationException("Failed to read file: " + file, e);
}
}

private String loadJsonFromResources(String baselineFile) {
URL resourceUrl = getClass().getClassLoader().getResource(baselineFile);
if (resourceUrl == null) {
throw new ValidationException("File not found: " + baselineFile);
}
File file = new File(resourceUrl.getFile());
return loadJsonFromFile(file.getAbsolutePath());
}

static class ActualBenchmarkResult {
String benchmark;
PrimaryMetric primaryMetric;

static class PrimaryMetric {
Map<String, Double> scorePercentiles;
}
}

static class BaselineResult {
Map<String, BenchmarkResult> benchmarkResultMap;

static class BenchmarkResult {
List<Percentile> scorePercentiles;

static class Percentile {
String percentile;
Double baseline;
Double difference;
}
}
}

static class ValidationException extends RuntimeException {
ValidationException(String message) {
super(message);
}

ValidationException(String message, Throwable cause) {
super(message, cause);
}
}

private static String parseCommandLineArg(String arg) {
if (arg == null || arg.isEmpty()) {
return "";
}
String[] args = arg.split("=");
if (args.length != 2) {
return "";
}
return args[1];
}

public static void main(String[] args) {
String actualFile = parseCommandLineArg(args[0]);
new BenchmarkValidator("com/google/cloud/spanner/jmh/jmh-baseline.json", actualFile).validate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.spanner.benchmarking;

import com.google.monitoring.v3.CreateTimeSeriesRequest;
import com.google.monitoring.v3.MetricServiceGrpc.MetricServiceImplBase;
import com.google.protobuf.Empty;
import io.grpc.stub.StreamObserver;

class MonitoringServiceImpl extends MetricServiceImplBase {

@Override
public void createServiceTimeSeries(
CreateTimeSeriesRequest request, StreamObserver<Empty> responseObserver) {
try {
Thread.sleep(100);
responseObserver.onNext(Empty.getDefaultInstance());
responseObserver.onCompleted();
} catch (InterruptedException e) {
responseObserver.onError(e);
}
}
}
Loading