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
+
+
+ URL |
+ View Count |
+
+ <%= BigQueryHome.getMostRecentRun() %>
+
+ Run Metric Values
+
+
+ Metric Type |
+ Count |
+ Most Recent End Time |
+ Most Recent Value |
+ Average/Values |
+
+ <%= BigQueryHome.getMetricAverages() %>
+
+
+
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);
+ }
+}