diff --git a/appengine-java21/ee8/bigquery/README.md b/appengine-java21/ee8/bigquery/README.md new file mode 100644 index 00000000000..4e557b6e61a --- /dev/null +++ b/appengine-java21/ee8/bigquery/README.md @@ -0,0 +1,65 @@ + +Open in Cloud Shell + +# Google Cloud API Showcase: BigQuery & Cloud Monitoring in App Engine standard environment for Java 21 + +This API Showcase demonstrates how to run an App Engine standard environment application with dependencies on both +[Google BigQuery][bigquery] and [Cloud Monitoring][monitoring]. + +[bigquery]: https://cloud.google.com/bigquery/docs +[monitoring]: https://cloud.google.com/monitoring/docs + +The home page of this application provides a form to initiate a query of public data, in this case StackOverflow +questions tagged with `google-bigquery`. + +The home page also provides a summary view of the metrics that have been logged in the past 30 days. + +## Clone the sample app + +Copy the sample apps to your local machine, and cd to the `appengine-java21/bigquery` directory: + +``` +git clone https://github.com/GoogleCloudPlatform/java-docs-samples +cd appengine-java21/bigquery +``` + +## Setup + +- Make sure [`gcloud`](https://cloud.google.com/sdk/docs/) is installed and initialized: +``` + gcloud init +``` +- If this is the first time you are creating an App Engine project +``` + gcloud app create +``` +- For local development, [set up][set-up] authentication +- Enable [BigQuery][bigquery-api] and [Monitoring][monitoring-api] APIs + +[set-up]: https://cloud.google.com/docs/authentication/getting-started +[bigquery-api]: https://console.cloud.google.com/launcher/details/google/bigquery-json.googleapis.com +[monitoring-api]: https://console.cloud.google.com/launcher/details/google/monitoring.googleapis.com + +## Run locally +Run using shown Maven command. You can then direct your browser to `http://localhost:8080/` to see the most recent query +run (since the app started) and the metrics from the past 30 days. + +``` +mvn appengine:run +``` + +Note: The first time the app is run (or after any metrics definitions have been deleted) it may take up to 5 minutes for +the MetricDescriptors to sync with StackDriver before any results are shown. If you do not see results, please wait a +few moments and try again. + +## Deploy + +- Deploy to App Engine standard environment using the following Maven command. +``` + mvn clean package appengine:deploy +``` +- Direct your browser to `https://.appspot.com`. +- View more in-depth metrics data on the [Cloud Monitoring Dashboard][dashboard] + +[dashboard]: https://pantheon.corp.google.com/monitoring + diff --git a/appengine-java21/ee8/bigquery/pom.xml b/appengine-java21/ee8/bigquery/pom.xml new file mode 100644 index 00000000000..e9c4723bca1 --- /dev/null +++ b/appengine-java21/ee8/bigquery/pom.xml @@ -0,0 +1,138 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-bigquery-monitoring-j21 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.28.0 + + + + + + + com.google.appengine + appengine-api-1.0-sdk + 2.0.23 + + + javax.servlet + javax.servlet-api + 3.1.0 + jar + provided + + + + com.google.cloud + google-cloud-bigquery + + + com.google.cloud + google-cloud-monitoring + + + + + com.google.appengine + appengine-api-stubs + 2.0.23 + test + + + com.google.appengine + appengine-tools-sdk + 2.0.23 + test + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + com.google.appengine + appengine-testing + 2.0.23 + test + + + com.google.truth + truth + 1.1.5 + test + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + com.google.cloud.tools + appengine-maven-plugin + 2.5.0 + + GCLOUD_CONFIG + GCLOUD_CONFIG + true + true + + + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + + + diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryHome.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryHome.java new file mode 100644 index 00000000000..91884d9cb72 --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryHome.java @@ -0,0 +1,76 @@ +/* + * Copyright 2018 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 + * + * http://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.example.appengine.bigquerylogging; + +import com.google.cloud.bigquery.FieldValueList; +import com.google.cloud.bigquery.TableResult; +import java.io.IOException; +import java.util.List; + +public class BigQueryHome { + private static BigQueryRunner queryRunner; + + private static BigQueryRunner getQueryRunner() throws IOException { + if (queryRunner == null) { + queryRunner = BigQueryRunner.getInstance(); + } + return queryRunner; + } + + public static String getMostRecentRun() throws IOException { + return convertRunToHtmlTable(BigQueryRunner.getMostRecentRunResult()); + } + + public static String getMetricAverages() throws IOException { + return convertAveragesToHtmlTable(getQueryRunner().getTimeSeriesValues()); + } + + private static String convertRunToHtmlTable(TableResult result) { + if (result == null) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + for (FieldValueList row : result.iterateAll()) { + sb.append(""); + String url = row.get("url").getStringValue(); + addColumn(sb, String.format("%s", url, url)); + addColumn(sb, row.get("view_count").getLongValue()); + sb.append(""); + } + return sb.toString(); + } + + private static String convertAveragesToHtmlTable(List> values) { + + StringBuilder sb = new StringBuilder(); + for (TimeSeriesSummary metric : values) { + sb.append(""); + addColumn(sb, metric.getName()); + addColumn(sb, metric.getValues().size()); + addColumn(sb, metric.getMostRecentRunTime()); + addColumn(sb, metric.getMostRecentValue()); + addColumn(sb, metric.getAverage()); + sb.append(""); + } + return sb.toString(); + } + + private static void addColumn(StringBuilder sb, T content) { + sb.append("").append(content.toString()).append(""); + } +} diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRun.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRun.java new file mode 100644 index 00000000000..fa83033229e --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRun.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 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 + * + * http://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.example.appengine.bigquerylogging; + +import java.io.IOException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(name = "runQuery BigQuery", value = "/bigquery/run") +public class BigQueryRun extends HttpServlet { + private BigQueryRunner queryRunner; + + public BigQueryRun() throws IOException { + this.queryRunner = BigQueryRunner.getInstance(); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + try { + queryRunner.runQuery(); + } catch (InterruptedException e) { + resp.sendError( + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Interrupted while running BigQuery job."); + } + // redirect to home page + resp.sendRedirect("/"); + } +} diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRunner.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRunner.java new file mode 100644 index 00000000000..0e4702e12ae --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRunner.java @@ -0,0 +1,255 @@ +/* + * Copyright 2018 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 + * + * http://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.example.appengine.bigquerylogging; + +import com.google.api.Metric; +import com.google.api.MetricDescriptor; +import com.google.cloud.ServiceOptions; +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.Job; +import com.google.cloud.bigquery.JobId; +import com.google.cloud.bigquery.JobInfo; +import com.google.cloud.bigquery.QueryJobConfiguration; +import com.google.cloud.bigquery.TableResult; +import com.google.cloud.monitoring.v3.MetricServiceClient; +import com.google.cloud.monitoring.v3.MetricServiceClient.ListMetricDescriptorsPagedResponse; +import com.google.cloud.monitoring.v3.MetricServiceClient.ListTimeSeriesPagedResponse; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.monitoring.v3.CreateMetricDescriptorRequest; +import com.google.monitoring.v3.CreateTimeSeriesRequest; +import com.google.monitoring.v3.ListMetricDescriptorsRequest; +import com.google.monitoring.v3.ListTimeSeriesRequest; +import com.google.monitoring.v3.Point; +import com.google.monitoring.v3.TimeInterval; +import com.google.monitoring.v3.TimeSeries; +import com.google.monitoring.v3.TypedValue; +import com.google.protobuf.util.Timestamps; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +public class BigQueryRunner { + private static final String CUSTOM_METRIC_FILTER = + "metric.type = starts_with(\"custom.googleapis.com/\")"; + private static BigQueryRunner instance; + + private static final MetricDescriptor QUERY_DURATION_METRIC = + MetricDescriptor.newBuilder() + .setName("custom.googleapis.com/queryDuration") + .setType("custom.googleapis.com/queryDuration") + .setDisplayName("queryDuration") + .setDescription("Time it took a query to run.") + .setMetricKind(MetricDescriptor.MetricKind.GAUGE) + .setValueType(MetricDescriptor.ValueType.INT64) + .build(); + private static final MetricDescriptor ROWS_RETURNED_METRIC = + MetricDescriptor.newBuilder() + .setName("custom.googleapis.com/rowsReturned") + .setType("custom.googleapis.com/rowsReturned") + .setDisplayName("rowsReturned") + .setDescription("Total rows returned by the query result.") + .setMetricKind(MetricDescriptor.MetricKind.GAUGE) + .setValueType(MetricDescriptor.ValueType.INT64) + .build(); + private static final Set REQUIRED_METRICS = + ImmutableSet.of(QUERY_DURATION_METRIC, ROWS_RETURNED_METRIC); + + private static TableResult mostRecentRunResult; + private static Set existingMetrics = Sets.newHashSet(); + + private final MetricServiceClient client; + private final BigQuery bigquery; + private final String projectName; + private PrintStream os; + + // Retrieve a singleton instance + public static synchronized BigQueryRunner getInstance() throws IOException { + if (instance == null) { + instance = new BigQueryRunner(); + } + return instance; + } + + private BigQueryRunner() throws IOException { + this( + MetricServiceClient.create(), + BigQueryOptions.getDefaultInstance().getService(), + System.out); + } + + BigQueryRunner(MetricServiceClient metricsClient, BigQuery bigquery, PrintStream os) { + client = metricsClient; + this.os = os; + this.projectName = String.format("projects/%s", ServiceOptions.getDefaultProjectId()); + this.bigquery = bigquery; + } + + public static TableResult getMostRecentRunResult() { + return mostRecentRunResult; + } + + public void runQuery() throws InterruptedException { + QueryJobConfiguration queryConfig = + QueryJobConfiguration.newBuilder( + "SELECT " + + "CONCAT('https://stackoverflow.com/questions/', CAST(id as STRING)) as url, " + + "view_count " + + "FROM `bigquery-public-data.stackoverflow.posts_questions` " + + "WHERE tags like '%google-bigquery%' " + + "ORDER BY favorite_count DESC LIMIT 10") + // Use standard SQL syntax for queries. + // See: https://cloud.google.com/bigquery/sql-reference/ + .setUseLegacySql(false) + .build(); + + List timeSeriesList = new ArrayList<>(); + + long queryStartTime = System.currentTimeMillis(); + + // Create a job ID so that we can safely retry. + JobId jobId = JobId.of(UUID.randomUUID().toString()); + Job queryJob = bigquery.create(JobInfo.newBuilder(queryConfig).setJobId(jobId).build()); + + // Wait for the query to complete. + queryJob = queryJob.waitFor(); + + // Check for errors + if (queryJob == null) { + throw new RuntimeException("Job no longer exists"); + } else if (queryJob.getStatus().getError() != null) { + // You can also look at queryJob.getStatus().getExecutionErrors() for all + // errors, not just the latest one. + throw new RuntimeException(queryJob.getStatus().getError().toString()); + } + + // Log the result metrics. + TableResult result = queryJob.getQueryResults(); + + long queryEndTime = System.currentTimeMillis(); + // Add query duration metric. + timeSeriesList.add(prepareMetric(QUERY_DURATION_METRIC, queryEndTime - queryStartTime)); + + // Add rows returned metric. + timeSeriesList.add(prepareMetric(ROWS_RETURNED_METRIC, result.getTotalRows())); + + // Prepares the time series request + CreateTimeSeriesRequest request = + CreateTimeSeriesRequest.newBuilder() + .setName(projectName) + .addAllTimeSeries(timeSeriesList) + .build(); + + createMetricsIfNeeded(); + client.createTimeSeries(request); + os.println("Done writing metrics."); + + mostRecentRunResult = result; + } + + // Returns a metric time series with a single int64 data point. + private TimeSeries prepareMetric(MetricDescriptor requiredMetric, long metricValue) { + TimeInterval interval = + TimeInterval.newBuilder() + .setEndTime(Timestamps.fromMillis(System.currentTimeMillis())) + .build(); + TypedValue value = TypedValue.newBuilder().setInt64Value(metricValue).build(); + + Point point = Point.newBuilder().setInterval(interval).setValue(value).build(); + + List pointList = Lists.newArrayList(); + pointList.add(point); + + Metric metric = Metric.newBuilder().setType(requiredMetric.getName()).build(); + + return TimeSeries.newBuilder().setMetric(metric).addAllPoints(pointList).build(); + } + + public List> getTimeSeriesValues() { + List> summaries = Lists.newArrayList(); + createMetricsIfNeeded(); + for (MetricDescriptor metric : REQUIRED_METRICS) { + ListTimeSeriesRequest listTimeSeriesRequest = + ListTimeSeriesRequest.newBuilder() + .setName(projectName) + .setFilter(String.format("metric.type = \"%s\"", metric.getType())) + .setInterval( + TimeInterval.newBuilder() + .setStartTime( + Timestamps.subtract( + Timestamps.fromMillis(System.currentTimeMillis()), + com.google.protobuf.Duration.newBuilder() + .setSeconds(60L * 60L * 24L * 30L) // 30 days ago + .build())) + .setEndTime(Timestamps.fromMillis(System.currentTimeMillis())) + .build()) + .build(); + try { + ListTimeSeriesPagedResponse listTimeSeriesResponse = + client.listTimeSeries(listTimeSeriesRequest); + ArrayList timeSeries = Lists.newArrayList(listTimeSeriesResponse.iterateAll()); + summaries.addAll( + timeSeries.stream() + .map(TimeSeriesSummary::fromTimeSeries) + .collect(Collectors.toList())); + } catch (RuntimeException ex) { + os.println("MetricDescriptors not yet synced. Please try again in a moment."); + } + } + return summaries; + } + + private void createMetricsIfNeeded() { + // If all required metrics already exist, no need to make service calls. + if (REQUIRED_METRICS.stream() + .map(MetricDescriptor::getDisplayName) + .allMatch(existingMetrics::contains)) { + return; + } + ListMetricDescriptorsRequest listMetricsRequest = + ListMetricDescriptorsRequest.newBuilder() + .setName(projectName) + .setFilter(CUSTOM_METRIC_FILTER) + .build(); + ListMetricDescriptorsPagedResponse listMetricsResponse = + client.listMetricDescriptors(listMetricsRequest); + + for (MetricDescriptor existingMetric : listMetricsResponse.iterateAll()) { + existingMetrics.add(existingMetric.getDisplayName()); + } + + REQUIRED_METRICS.stream() + .filter(metric -> !existingMetrics.contains(metric.getDisplayName())) + .forEach(this::createMetric); + } + + private void createMetric(MetricDescriptor newMetric) { + CreateMetricDescriptorRequest request = + CreateMetricDescriptorRequest.newBuilder() + .setName(projectName) + .setMetricDescriptor(newMetric) + .build(); + + client.createMetricDescriptor(request); + } +} diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/TimeSeriesSummary.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/TimeSeriesSummary.java new file mode 100644 index 00000000000..04e2e23e543 --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/TimeSeriesSummary.java @@ -0,0 +1,116 @@ +/* + * Copyright 2018 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 + * + * http://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.example.appengine.bigquerylogging; + +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.monitoring.v3.Point; +import com.google.monitoring.v3.TimeSeries; +import com.google.protobuf.Timestamp; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class TimeSeriesSummary { + private String name; + private Timestamp mostRecentRunTime; + T mostRecentValue; + List values; + + public static TimeSeriesSummary fromTimeSeries(TimeSeries timeSeries) { + switch (timeSeries.getValueType()) { + case STRING: + return new StringTimeSeriesSummary(timeSeries); + case INT64: + return new Int64TimeSeriesSummary(timeSeries); + default: + return null; + } + } + + private TimeSeriesSummary(TimeSeries timeSeries) { + name = timeSeries.getMetric().getType(); + } + + Point getMostRecentPoint(TimeSeries timeSeries) { + Point max = + Collections.max( + timeSeries.getPointsList(), + Comparator.comparingLong(p -> p.getInterval().getEndTime().getSeconds())); + mostRecentRunTime = max.getInterval().getEndTime(); + return max; + } + + public String getName() { + return name; + } + + public T getMostRecentValue() { + return mostRecentValue; + } + + public Timestamp getMostRecentRunTime() { + return mostRecentRunTime; + } + + public List getValues() { + return values; + } + + public abstract T getAverage(); + + public static class StringTimeSeriesSummary extends TimeSeriesSummary { + private StringTimeSeriesSummary(TimeSeries timeSeries) { + super(timeSeries); + Point max = getMostRecentPoint(timeSeries); + if (max == null) { + return; + } + mostRecentValue = max.getValue().getStringValue(); + values = + Lists.newArrayList( + Collections2.transform( + timeSeries.getPointsList(), point -> point.getValue().getStringValue())); + } + + @Override + public String getAverage() { + return values.stream().collect(Collectors.joining(",")); + } + } + + public static class Int64TimeSeriesSummary extends TimeSeriesSummary { + private Int64TimeSeriesSummary(TimeSeries timeSeries) { + super(timeSeries); + Point max = getMostRecentPoint(timeSeries); + if (max == null) { + return; + } + mostRecentValue = max.getValue().getInt64Value(); + values = + Lists.newArrayList( + Collections2.transform( + timeSeries.getPointsList(), point -> point.getValue().getInt64Value())); + } + + @Override + public Long getAverage() { + return values.stream().collect(Collectors.averagingLong(Long::longValue)).longValue(); + } + } +} diff --git a/appengine-java21/ee8/bigquery/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/bigquery/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..15fc6f754c4 --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,10 @@ + + + java21 + true + + + + + dzlier-work + \ No newline at end of file diff --git a/appengine-java21/ee8/bigquery/src/main/webapp/index.jsp b/appengine-java21/ee8/bigquery/src/main/webapp/index.jsp new file mode 100644 index 00000000000..af045fbf1fc --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/webapp/index.jsp @@ -0,0 +1,30 @@ +<%@ page import="com.example.appengine.bigquerylogging.BigQueryHome" %> + + + An example of using BigQuery and StackDriver Logging on AppEngine Standard + +

Run query

+
+ +
+

Most Recent Run Results

+ + + + + + <%= BigQueryHome.getMostRecentRun() %> +
URLView Count
+

Run Metric Values

+ + + + + + + + + <%= BigQueryHome.getMetricAverages() %> +
Metric TypeCountMost Recent End TimeMost Recent ValueAverage/Values
+ + diff --git a/appengine-java21/ee8/bigquery/src/test/java/com/example/appengine/bigquerylogging/BigQueryRunnerTest.java b/appengine-java21/ee8/bigquery/src/test/java/com/example/appengine/bigquerylogging/BigQueryRunnerTest.java new file mode 100644 index 00000000000..1332c3afa05 --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/test/java/com/example/appengine/bigquerylogging/BigQueryRunnerTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2018 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 + * + * http://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.example.appengine.bigquerylogging; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.MetricDescriptor; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.monitoring.v3.MetricServiceClient; +import com.google.cloud.monitoring.v3.MetricServiceClient.ListMetricDescriptorsPagedResponse; +import com.google.cloud.monitoring.v3.stub.MetricServiceStub; +import com.google.monitoring.v3.CreateMetricDescriptorRequest; +import com.google.monitoring.v3.CreateTimeSeriesRequest; +import com.google.monitoring.v3.ListMetricDescriptorsRequest; +import com.google.protobuf.Empty; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Collections; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for simple app sample. */ +@RunWith(JUnit4.class) +public class BigQueryRunnerTest { + private ByteArrayOutputStream bout; + private BigQueryRunner app; + + @Mock private MetricServiceStub metricsServiceStub; + + @Mock + private UnaryCallable + listCallable; + + @Mock private UnaryCallable createMetricCallable; + @Mock private UnaryCallable createTimeSeriesCallable; + @Mock private ListMetricDescriptorsPagedResponse listResponse; + + @Captor private ArgumentCaptor createTimeSeriesRequest; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + + MetricServiceClient metricsClient = MetricServiceClient.create(metricsServiceStub); + app = new BigQueryRunner(metricsClient, BigQueryOptions.getDefaultInstance().getService(), out); + + when(metricsServiceStub.listMetricDescriptorsPagedCallable()).thenReturn(listCallable); + when(listCallable.call(any(ListMetricDescriptorsRequest.class))).thenReturn(listResponse); + when(listResponse.iterateAll()).thenReturn(Collections.emptyList()); + + when(metricsServiceStub.createMetricDescriptorCallable()).thenReturn(createMetricCallable); + when(createMetricCallable.call(any(CreateMetricDescriptorRequest.class))).thenReturn(null); + + when(metricsServiceStub.createTimeSeriesCallable()).thenReturn(createTimeSeriesCallable); + when(createTimeSeriesCallable.call(any(CreateTimeSeriesRequest.class))) + .thenReturn(Empty.getDefaultInstance()); + } + + @Test + public void testRun() throws Exception { + app.runQuery(); + String got = bout.toString(); + assertThat(got).contains("Done writing metrics."); + verify(metricsServiceStub).listMetricDescriptorsPagedCallable(); + + verify(metricsServiceStub, times(2)).createMetricDescriptorCallable(); + + verify(metricsServiceStub).createTimeSeriesCallable(); + verify(createTimeSeriesCallable).call(createTimeSeriesRequest.capture()); + CreateTimeSeriesRequest actual = createTimeSeriesRequest.getValue(); + assertEquals(2, actual.getTimeSeriesCount()); + assertThat(actual.getTimeSeries(0).getMetric().getType()) + .isEqualTo("custom.googleapis.com/queryDuration"); + assertThat(actual.getTimeSeries(0).getPoints(0).getValue().getInt64Value()).isGreaterThan(0L); + assertThat(actual.getTimeSeries(1).getMetric().getType()) + .isEqualTo("custom.googleapis.com/rowsReturned"); + assertThat(actual.getTimeSeries(1).getPoints(0).getValue().getInt64Value()).isGreaterThan(0L); + } +}