Skip to content

Commit

Permalink
Implementing Generic Spanner templates Load test observer (#1858)
Browse files Browse the repository at this point in the history
* Implementing Generic Spanner templates observer

* Always run all Observers
  • Loading branch information
darshan-sj authored Sep 19, 2024
1 parent f27b88d commit fe87109
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 26 deletions.
9 changes: 9 additions & 0 deletions lt/observability/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
<configuration>
<!-- Always run all the observers -->
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public void assertAll(Runnable... assertions) throws MultipleFailureException {
for (Runnable assertion : assertions) {
try {
assertion.run();
} catch (AssertionError e) {
} catch (Throwable e) {
assertionErrors.add(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public Object getValue(Field columnSchema, FieldValue value) {
}
return returnVal;
} else {
if (value.isNull()) {
return null;
}
switch (columnSchema.getType().getStandardType()) {
case BOOL:
return value.getBooleanValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,44 +27,51 @@ public abstract class DetectMetricDeviationConditionCheck extends ConditionCheck

abstract double percentageDeviation();

abstract String testCaseDescription();

@Override
public String getDescription() {
return String.format("DetectMetricDeviation ConditionCheck for %s", metricName());
return String.format("DetectMetricDeviation for %s", metricName());
}

@Override
protected CheckResult check() {
double totalMetricValue = 0.0;
PerfResultRow latestRow = null;
for (PerfResultRow perfResultRow : dataset().rows()) {
if (latestRow == null) {
latestRow = perfResultRow;
} else {
totalMetricValue += getMetricValue(perfResultRow, metricName());
try {
for (PerfResultRow perfResultRow : dataset().rows()) {
if (latestRow == null) {
latestRow = perfResultRow;
} else {
totalMetricValue += getMetricValue(perfResultRow, metricName());
}
}
}
double average = totalMetricValue / (dataset().rows().size() - 1.0);

// Deviating more than x% of average
if ((getMetricValue(latestRow, metricName())
> average * (100.0 + percentageDeviation()) / 100.0)
|| (getMetricValue(latestRow, metricName())
< average * (100.0 - percentageDeviation()) / 100.0)) {
return new ConditionCheck.CheckResult(
false,
String.format(
"Metric %s is deviating more than %.2f %% than the average. %s=%f, Average=%f\n",
metricName(),
percentageDeviation(),
metricName(),
getMetricValue(latestRow, metricName()),
average));
double average = totalMetricValue / (dataset().rows().size() - 1.0);

// Deviating more than x% of average
if ((getMetricValue(latestRow, metricName())
> average * (100.0 + percentageDeviation()) / 100.0)
|| (getMetricValue(latestRow, metricName())
< average * (100.0 - percentageDeviation()) / 100.0)) {
return new ConditionCheck.CheckResult(
false,
String.format(
"Metric %s is deviating more than %.2f %% than the average. %s=%f, Average=%f\n",
metricName(),
percentageDeviation(),
metricName(),
getMetricValue(latestRow, metricName()),
average));
}
} catch (MetricNotFoundException e) {
LOG.warn("Metric " + metricName() + " not found in Dataset! Ignoring the check Condition");
}

return new ConditionCheck.CheckResult(true);
}

public double getMetricValue(PerfResultRow resultRow, String metricName) {
public double getMetricValue(PerfResultRow resultRow, String metricName)
throws MetricNotFoundException {
if (resultRow.row.containsKey("metrics")) {
List<Object> metrics = (List<Object>) resultRow.row.get("metrics");
for (Object metric : metrics) {
Expand All @@ -74,7 +81,7 @@ public double getMetricValue(PerfResultRow resultRow, String metricName) {
}
}
}
throw new IllegalArgumentException("Metric " + metricName + " not found!");
throw new MetricNotFoundException("Metric " + metricName + " not found!");
}

public static Builder builder() {
Expand All @@ -96,6 +103,23 @@ public static String constructQuery(String templateName, String testName, String
numRows);
}

public void assertTrue() {
if (!this.get()) {
throw new AssertionError(
String.format(
"Metric %s is deviating more than %.2f %% than the average for %s\n",
metricName(), percentageDeviation(), testCaseDescription()));
}
}

public static class MetricNotFoundException extends Exception {
public MetricNotFoundException() {}

public MetricNotFoundException(String message) {
super(message);
}
}

@AutoValue.Builder
public abstract static class Builder {

Expand All @@ -105,6 +129,8 @@ public abstract static class Builder {

public abstract Builder setPercentageDeviation(double percentageDeviation);

public abstract Builder setTestCaseDescription(String description);

abstract DetectMetricDeviationConditionCheck autoBuild();

public DetectMetricDeviationConditionCheck build() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,19 @@ public void observeDSToSpanner100GBLT() throws MultipleFailureException {
.setDataset(dataset)
.setMetricName("RunTime")
.setPercentageDeviation(25)
.setTestCaseDescription(
String.format(
"templateName=%s, testName=%s", "cloud_datastream_to_spanner", "backfill100Gb"))
.build();

DetectMetricDeviationConditionCheck totalCostCheck =
DetectMetricDeviationConditionCheck.builder()
.setDataset(dataset)
.setMetricName("EstimatedCost")
.setPercentageDeviation(25)
.setTestCaseDescription(
String.format(
"templateName=%s, testName=%s", "cloud_datastream_to_spanner", "backfill100Gb"))
.build();

AssertAll asserts = new AssertAll();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.google.cloud.teleport.lt.spanner;

import com.google.auth.Credentials;
import com.google.cloud.teleport.lt.AssertAll;
import com.google.cloud.teleport.lt.dataset.bigquery.BigQueryPerfDataset;
import com.google.cloud.teleport.lt.dataset.bigquery.BigQueryPerfDatasetFetcher;
import com.google.cloud.teleport.lt.dataset.bigquery.PerfResultRow;
import com.google.cloud.teleport.lt.rules.DetectMetricDeviationConditionCheck;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.beam.it.common.TestProperties;
import org.apache.beam.it.gcp.bigquery.BigQueryResourceManager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.model.MultipleFailureException;

/** Load test observer for all the spanner templates. */
@RunWith(Parameterized.class)
public class GenericTemplateLTObserver {

private static final Credentials CREDENTIALS = TestProperties.googleCredentials();

private static BigQueryResourceManager bigQueryResourceManager;

@Parameters
public static Collection<String[]> getParameters() {
String projectId = TestProperties.exportProject();
bigQueryResourceManager =
BigQueryResourceManager.builder("Load-test-analyzer", projectId, CREDENTIALS)
.setDatasetId(TestProperties.exportDataset())
.build();
// *DO NOT CALL cleanup on this bigQueryResourceManager.* It would delete the tables in the
// dataset.
return getSpannerTemplateAndTestNames().stream()
.map(x -> x.toArray(new String[0]))
.collect(Collectors.toList());
}

@Parameter(0)
public String templateName;

@Parameter(1)
public String testName;

@Test
public void observeLoadTest() throws MultipleFailureException {
String query = DetectMetricDeviationConditionCheck.constructQuery(templateName, testName, "11");
// Fetch data
BigQueryPerfDatasetFetcher datasetFetcher =
BigQueryPerfDatasetFetcher.builder()
.setQuery(query)
.setBigQueryResourceManager(bigQueryResourceManager)
.build();
BigQueryPerfDataset dataset = datasetFetcher.fetch();

// Assert condition checks
List<Runnable> assertions = new ArrayList<>();
DetectMetricDeviationConditionCheck totalTimeCheck =
createMetricDeviationConditionCheck("RunTime", dataset, templateName, testName);
assertions.add(totalTimeCheck::assertTrue);

AssertAll asserts = new AssertAll();
asserts.assertAll(assertions.toArray(new Runnable[0]));
}

private static List<List<String>> getSpannerTemplateAndTestNames() {
List<List<String>> allTemplates = getAllTemplateAndTestNames();
// relies on the template_name to contain "spanner" if it is a spanner template.
return allTemplates.stream()
.filter(x -> x.get(0).toLowerCase().contains("spanner"))
.collect(Collectors.toList());
}

private static List<List<String>> getAllTemplateAndTestNames() {
String query =
"SELECT template_name, test_name "
+ "FROM `%s.%s.%s` "
+ " GROUP BY template_name, test_name";
query =
String.format(
query,
TestProperties.exportProject(),
TestProperties.exportDataset(),
TestProperties.exportTable());

BigQueryPerfDatasetFetcher datasetFetcher =
BigQueryPerfDatasetFetcher.builder()
.setQuery(query)
.setBigQueryResourceManager(bigQueryResourceManager)
.build();
BigQueryPerfDataset dataset = datasetFetcher.fetch();

List<List<String>> result = new ArrayList<>();

for (PerfResultRow resultRow : dataset.rows()) {
Object templateName = resultRow.row.get("template_name");
Object testName = resultRow.row.get("test_name");
if (templateName != null && testName != null) {
result.add(List.of(templateName.toString(), testName.toString()));
}
}
return result;
}

private DetectMetricDeviationConditionCheck createMetricDeviationConditionCheck(
String metricName, BigQueryPerfDataset dataset, String templateName, String testName) {
return DetectMetricDeviationConditionCheck.builder()
.setDataset(dataset)
.setMetricName(metricName)
.setPercentageDeviation(25)
.setTestCaseDescription(
String.format("templateName=%s testName=%s ", templateName, testName))
.build();
}
}

0 comments on commit fe87109

Please sign in to comment.