This is an automated email from the ASF dual-hosted git repository.
tuglu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 4b624b2cb47 Emit status code tag in (sqlQuery|query)/time metrics
(#18631)
4b624b2cb47 is described below
commit 4b624b2cb47071d20dc4d0fd03bc027551f56b92
Author: jtuglu1 <[email protected]>
AuthorDate: Tue Oct 14 18:35:30 2025 -0700
Emit status code tag in (sqlQuery|query)/time metrics (#18631)
This adds a code dimension to the query/time and sqlQuery/time metrics
which reports the result status code of the query. This is more verbose/helpful
than success true/false and allows you to do quick aggregations filtering on
code.
---
docs/operations/metrics.md | 4 +-
.../apache/druid/query/DefaultQueryMetrics.java | 7 ++
.../java/org/apache/druid/query/DruidMetrics.java | 26 ++++++
.../java/org/apache/druid/query/QueryMetrics.java | 7 ++
.../query/search/DefaultSearchQueryMetrics.java | 8 ++
.../org/apache/druid/query/DruidMetricsTest.java | 48 +++++++++++
.../org/apache/druid/server/QueryLifecycle.java | 2 +
.../org/apache/druid/server/QueryResourceTest.java | 98 +++++++++++++++++++---
.../druid/server/AsyncQueryForwardingServlet.java | 8 +-
.../server/AsyncQueryForwardingServletTest.java | 7 +-
.../org/apache/druid/sql/SqlExecutionReporter.java | 6 ++
.../org/apache/druid/sql/http/SqlResourceTest.java | 31 +++++++
12 files changed, 236 insertions(+), 16 deletions(-)
diff --git a/docs/operations/metrics.md b/docs/operations/metrics.md
index 55a6dd449b3..f5fdcecccd3 100644
--- a/docs/operations/metrics.md
+++ b/docs/operations/metrics.md
@@ -45,7 +45,7 @@ Most metric values reset each emission period, as specified
in `druid.monitoring
|Metric|Description|Dimensions|Normal value|
|------|-----------|----------|------------|
-|`query/time`|Milliseconds taken to complete a query.|Native Query:
`dataSource`, `type`, `interval`, `hasFilters`, `duration`, `context`,
`remoteAddress`, `id`.|< 1s|
+|`query/time`|Milliseconds taken to complete a query.|Native Query:
`dataSource`, `type`, `interval`, `hasFilters`, `duration`, `context`,
`remoteAddress`, `id`, `code`.|< 1s|
### Broker
@@ -64,7 +64,7 @@ Most metric values reset each emission period, as specified
in `druid.monitoring
|`query/timeout/count`|Number of timed out queries.|This metric is only
available if the `QueryCountStatsMonitor` module is included.| |
|`query/segments/count`|This metric is not enabled by default. See the
`QueryMetrics` Interface for reference regarding enabling this metric. Number
of segments that will be touched by the query. In the broker, it makes a plan
to distribute the query to realtime tasks and historicals based on a snapshot
of segment distribution state. If there are some segments moved after this
snapshot is created, certain historicals and realtime tasks can report those
segments as missing to the broker. [...]
|`query/priority`|Assigned lane and priority, only if Laning strategy is
enabled. Refer to [Laning
strategies](../configuration/index.md#laning-strategies)|`lane`, `dataSource`,
`type`|0|
-|`sqlQuery/time`|Milliseconds taken to complete a SQL query.|`id`,
`nativeQueryIds`, `dataSource`, `remoteAddress`, `success`, `engine`|< 1s|
+|`sqlQuery/time`|Milliseconds taken to complete a SQL query.|`id`,
`nativeQueryIds`, `dataSource`, `remoteAddress`, `success`, `engine`, `code`|<
1s|
|`sqlQuery/planningTimeMs`|Milliseconds taken to plan a SQL to native
query.|`id`, `nativeQueryIds`, `dataSource`, `remoteAddress`, `success`,
`engine`| |
|`sqlQuery/bytes`|Number of bytes returned in the SQL query response.|`id`,
`nativeQueryIds`, `dataSource`, `remoteAddress`, `success`, `engine`| |
|`serverview/init/time`|Time taken to initialize the broker server view.
Useful to detect if brokers are taking too long to start.||Depends on the
number of segments.|
diff --git
a/processing/src/main/java/org/apache/druid/query/DefaultQueryMetrics.java
b/processing/src/main/java/org/apache/druid/query/DefaultQueryMetrics.java
index 9aab67e9f1a..624a6f783fe 100644
--- a/processing/src/main/java/org/apache/druid/query/DefaultQueryMetrics.java
+++ b/processing/src/main/java/org/apache/druid/query/DefaultQueryMetrics.java
@@ -27,6 +27,7 @@ import
org.apache.druid.java.util.emitter.service.ServiceEmitter;
import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
import org.joda.time.Interval;
+import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -193,6 +194,12 @@ public class DefaultQueryMetrics<QueryType extends
Query<?>> implements QueryMet
setDimension("success", String.valueOf(success));
}
+ @Override
+ public void code(@Nullable Throwable error)
+ {
+ setDimension(DruidMetrics.CODE, DruidMetrics.computeStatusCode(error));
+ }
+
@Override
public void segment(String segmentIdentifier)
{
diff --git a/processing/src/main/java/org/apache/druid/query/DruidMetrics.java
b/processing/src/main/java/org/apache/druid/query/DruidMetrics.java
index 923eb11656f..61c4733a64e 100644
--- a/processing/src/main/java/org/apache/druid/query/DruidMetrics.java
+++ b/processing/src/main/java/org/apache/druid/query/DruidMetrics.java
@@ -19,9 +19,11 @@
package org.apache.druid.query;
+import org.apache.druid.error.DruidException;
import org.apache.druid.query.aggregation.AggregatorFactory;
import org.apache.druid.segment.column.ValueType;
+import javax.annotation.Nullable;
import java.util.List;
/**
@@ -35,6 +37,7 @@ public class DruidMetrics
public static final String INTERVAL = "interval";
public static final String ID = "id";
public static final String SUBQUERY_ID = "subQueryId";
+ public static final String CODE = "code";
public static final String STATUS = "status";
public static final String ENGINE = "engine";
public static final String DURATION = "duration";
@@ -87,4 +90,27 @@ public class DruidMetrics
queryMetrics.remoteAddress(remoteAddr);
return queryMetrics;
}
+
+ /**
+ * Computes the HTTP status code based on the query error (if any) for
tagged metric emission.
+ * <ul>
+ * <li>If error is null: returns 200 (success)</li>
+ * <li>If error is a DruidException: returns the category's expected HTTP
status</li>
+ * <li>Otherwise (unclassified error): returns 500 (internal server
error)</li>
+ * </ul>
+ *
+ * @param error The throwable error, or null if successful
+ * @return HTTP status code appropriate for the error
+ */
+ public static int computeStatusCode(@Nullable Throwable error)
+ {
+ if (error == null) {
+ return 200;
+ }
+ if (error instanceof DruidException) {
+ return ((DruidException) error).getCategory().getExpectedStatus();
+ }
+ // Unclassified errors default to 500 (defensive)
+ return DruidException.Category.DEFENSIVE.getExpectedStatus();
+ }
}
diff --git a/processing/src/main/java/org/apache/druid/query/QueryMetrics.java
b/processing/src/main/java/org/apache/druid/query/QueryMetrics.java
index 284da48c42b..403420b4b04 100644
--- a/processing/src/main/java/org/apache/druid/query/QueryMetrics.java
+++ b/processing/src/main/java/org/apache/druid/query/QueryMetrics.java
@@ -27,6 +27,7 @@ import org.apache.druid.query.filter.Filter;
import org.apache.druid.query.filter.FilterBundle;
import org.apache.druid.query.search.SearchQueryMetricsFactory;
+import javax.annotation.Nullable;
import java.util.List;
/**
@@ -241,6 +242,12 @@ public interface QueryMetrics<QueryType extends Query<?>>
void success(boolean success);
+ /**
+ * Translates the given query exception into the appropriate failure status
code.
+ * See {@link DruidMetrics#computeStatusCode}.
+ */
+ void code(@Nullable Throwable error);
+
void segment(String segmentIdentifier);
/**
diff --git
a/processing/src/main/java/org/apache/druid/query/search/DefaultSearchQueryMetrics.java
b/processing/src/main/java/org/apache/druid/query/search/DefaultSearchQueryMetrics.java
index 85696d69ef9..9a694824c3b 100644
---
a/processing/src/main/java/org/apache/druid/query/search/DefaultSearchQueryMetrics.java
+++
b/processing/src/main/java/org/apache/druid/query/search/DefaultSearchQueryMetrics.java
@@ -27,6 +27,8 @@ import org.apache.druid.query.Query;
import org.apache.druid.query.QueryMetrics;
import org.apache.druid.query.filter.FilterBundle;
+import javax.annotation.Nullable;
+
/**
* This class is implemented with delegation to another QueryMetrics for
compatibility, see "Making subinterfaces of
* QueryMetrics for emitting custom dimensions and/or metrics for specific
query types" section in {@link QueryMetrics}
@@ -145,6 +147,12 @@ public class DefaultSearchQueryMetrics implements
SearchQueryMetrics
delegateQueryMetrics.success(success);
}
+ @Override
+ public void code(@Nullable Throwable error)
+ {
+ delegateQueryMetrics.code(error);
+ }
+
@Override
public void segment(String segmentIdentifier)
{
diff --git
a/processing/src/test/java/org/apache/druid/query/DruidMetricsTest.java
b/processing/src/test/java/org/apache/druid/query/DruidMetricsTest.java
new file mode 100644
index 00000000000..0ee2ec9beb6
--- /dev/null
+++ b/processing/src/test/java/org/apache/druid/query/DruidMetricsTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.druid.query;
+
+import org.apache.druid.error.DruidException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DruidMetricsTest
+{
+ @Test
+ public void testComputeStatusCode_nullError()
+ {
+ Assert.assertEquals(200, DruidMetrics.computeStatusCode(null));
+ }
+
+ @Test
+ public void testComputeStatusCode_allDruidExceptionCategories()
+ {
+ for (DruidException.Category cat : DruidException.Category.values()) {
+ Assert.assertEquals(
+ cat.getExpectedStatus(), DruidMetrics.computeStatusCode(
+ DruidException.forPersona(DruidException.Persona.USER)
+ .ofCategory(cat)
+ .build("test")
+ )
+ );
+ }
+ }
+}
+
diff --git a/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
b/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
index b36a1167752..8fb3381f16f 100644
--- a/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
+++ b/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
@@ -401,6 +401,7 @@ public class QueryLifecycle
StringUtils.nullToEmptyNonDruidDataString(remoteAddress)
);
queryMetrics.success(success);
+ queryMetrics.code(e);
queryMetrics.reportQueryTime(queryTimeNs);
if (bytesWritten >= 0) {
@@ -417,6 +418,7 @@ public class QueryLifecycle
statsMap.put("query/time", TimeUnit.NANOSECONDS.toMillis(queryTimeNs));
statsMap.put("query/bytes", bytesWritten);
statsMap.put("success", success);
+ statsMap.put(DruidMetrics.CODE, DruidMetrics.computeStatusCode(e));
if (authenticationResult != null) {
statsMap.put("identity", authenticationResult.getIdentity());
diff --git
a/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
b/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
index 16fcf562ccd..1f116bc3963 100644
--- a/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
@@ -52,10 +52,13 @@ import org.apache.druid.java.util.common.guava.Yielders;
import org.apache.druid.java.util.common.guava.YieldingAccumulator;
import org.apache.druid.java.util.emitter.EmittingLogger;
import org.apache.druid.java.util.emitter.service.ServiceEmitter;
+import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
+import org.apache.druid.java.util.metrics.StubServiceEmitter;
import org.apache.druid.query.BadJsonQueryException;
import org.apache.druid.query.DefaultGenericQueryMetricsFactory;
import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.DefaultQueryRunnerFactoryConglomerate;
+import org.apache.druid.query.DruidMetrics;
import org.apache.druid.query.Query;
import org.apache.druid.query.QueryCapacityExceededException;
import org.apache.druid.query.QueryException;
@@ -126,6 +129,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
public class QueryResourceTest
{
@@ -233,6 +237,7 @@ public class QueryResourceTest
private QueryResource queryResource;
private QueryScheduler queryScheduler;
private TestRequestLogger testRequestLogger;
+ private StubServiceEmitter emitter;
@BeforeClass
public static void staticSetup()
@@ -253,6 +258,7 @@ public class QueryResourceTest
queryScheduler = QueryStackTests.DEFAULT_NOOP_SCHEDULER;
testRequestLogger = new TestRequestLogger();
+ emitter = new StubServiceEmitter();
queryResource = createQueryResource(ResponseContextConfig.newConfig(true));
}
@@ -263,7 +269,7 @@ public class QueryResourceTest
CONGLOMERATE,
TEST_SEGMENT_WALKER,
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
@@ -312,7 +318,7 @@ public class QueryResourceTest
CONGLOMERATE,
TEST_SEGMENT_WALKER,
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
@@ -392,7 +398,7 @@ public class QueryResourceTest
}
},
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
@@ -417,6 +423,8 @@ public class QueryResourceTest
final Response response =
expectSynchronousRequestFlow(SIMPLE_TIMESERIES_QUERY);
Assert.assertEquals(Status.INTERNAL_SERVER_ERROR.getStatusCode(),
response.getStatus());
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(500,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
final ErrorResponse entity = (ErrorResponse) response.getEntity();
MatcherAssert.assertThat(
@@ -490,7 +498,7 @@ public class QueryResourceTest
}
},
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
@@ -524,6 +532,9 @@ public class QueryResourceTest
Assert.assertTrue(fields.containsKey(QueryResource.RESPONSE_COMPLETE_TRAILER_HEADER));
Assert.assertEquals(fields.get(QueryResource.RESPONSE_COMPLETE_TRAILER_HEADER),
"false");
+
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(504,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -572,7 +583,7 @@ public class QueryResourceTest
}
},
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
@@ -604,6 +615,8 @@ public class QueryResourceTest
+ "\"errorMessage\":\"mid-flight exception\",\"context\":{}}]",
actualOutput
);
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(400,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -668,7 +681,7 @@ public class QueryResourceTest
}
},
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
@@ -705,6 +718,9 @@ public class QueryResourceTest
Assert.assertTrue(fields.containsKey(QueryResource.RESPONSE_COMPLETE_TRAILER_HEADER));
Assert.assertEquals("true",
fields.get(QueryResource.RESPONSE_COMPLETE_TRAILER_HEADER));
+
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(200,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@@ -748,7 +764,7 @@ public class QueryResourceTest
CONGLOMERATE,
querySegmentWalker,
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
overrideConfig,
@@ -761,6 +777,7 @@ public class QueryResourceTest
@Override
public void emitLogsAndMetrics(@Nullable Throwable e, @Nullable
String remoteAddress, long bytesWritten)
{
+ super.emitLogsAndMetrics(e, remoteAddress, bytesWritten);
Assert.assertTrue(Throwables.getStackTraceAsString(e).contains(embeddedExceptionMessage));
}
};
@@ -794,6 +811,8 @@ public class QueryResourceTest
)
.expectMessageIs("something")
);
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(500,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -807,7 +826,7 @@ public class QueryResourceTest
CONGLOMERATE,
TEST_SEGMENT_WALKER,
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
@@ -854,6 +873,8 @@ public class QueryResourceTest
-1,
testRequestLogger.getNativeQuerylogs().get(0).getQuery().getContext().get(overrideConfigKey)
);
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(200,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -878,6 +899,8 @@ public class QueryResourceTest
expectedException,
jsonMapper.readValue(response.baos.toByteArray(),
QueryInterruptedException.class).toString()
);
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(500,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -892,6 +915,10 @@ public class QueryResourceTest
queryResource
);
Assert.assertEquals(HttpStatus.SC_OK, response.getStatus());
+
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(1, queryResource.getSuccessfulQueryCount());
+ Assert.assertEquals(200,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -904,6 +931,10 @@ public class QueryResourceTest
Assert.assertEquals(HttpStatus.SC_OK, response.getStatus());
//since accept header is null, the response content type should be same as
the value of 'Content-Type' header
Assert.assertEquals(MediaType.APPLICATION_JSON, response.getContentType());
+
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(1, queryResource.getSuccessfulQueryCount());
+ Assert.assertEquals(200,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -917,6 +948,10 @@ public class QueryResourceTest
Assert.assertEquals(HttpStatus.SC_OK, response.getStatus());
//since accept header is empty, the response content type should be same
as the value of 'Content-Type' header
Assert.assertEquals(MediaType.APPLICATION_JSON, response.getContentType());
+
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(1, queryResource.getSuccessfulQueryCount());
+ Assert.assertEquals(200,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -932,6 +967,10 @@ public class QueryResourceTest
// Content-Type in response should be Smile
Assert.assertEquals(SmileMediaTypes.APPLICATION_JACKSON_SMILE,
response.getContentType());
+
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(1, queryResource.getSuccessfulQueryCount());
+ Assert.assertEquals(200,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -952,6 +991,10 @@ public class QueryResourceTest
// Content-Type in response should be Smile
Assert.assertEquals(SmileMediaTypes.APPLICATION_JACKSON_SMILE,
response.getContentType());
+
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(1, queryResource.getSuccessfulQueryCount());
+ Assert.assertEquals(200,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -971,6 +1014,10 @@ public class QueryResourceTest
// Content-Type in response should default to Content-Type from request
Assert.assertEquals(SmileMediaTypes.APPLICATION_JACKSON_SMILE,
response.getContentType());
+
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(1, queryResource.getSuccessfulQueryCount());
+ Assert.assertEquals(200,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -1131,7 +1178,7 @@ public class QueryResourceTest
CONGLOMERATE,
timeoutSegmentWalker,
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
@@ -1180,6 +1227,8 @@ public class QueryResourceTest
Assert.assertEquals(QueryException.QUERY_TIMEOUT_ERROR_CODE,
ex.getErrorCode());
Assert.assertEquals(1, timeoutQueryResource.getTimedOutQueryCount());
+ emitter.verifyEmitted("query/time", 1);
+ Assert.assertEquals(504,
emitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test(timeout = 60_000L)
@@ -1480,6 +1529,16 @@ public class QueryResourceTest
}
Assert.assertEquals(2, queryResource.getSuccessfulQueryCount());
Assert.assertEquals(1, queryResource.getFailedQueryCount());
+
+ emitter.verifyEmitted("query/time", 3);
+ Map<Integer, Long> codeFrequencies =
emitter.getMetricEvents("query/time").stream()
+ .map(ServiceMetricEvent::toMap)
+ .map(map -> (int)
map.get(DruidMetrics.CODE))
+ .collect(Collectors.groupingBy(
+ code -> code,
+ Collectors.counting()
+ ));
+ Assert.assertEquals(Map.of(200, 2L, 429, 1L), codeFrequencies);
}
@Test(timeout = 10_000L)
@@ -1550,6 +1609,16 @@ public class QueryResourceTest
for (Future<Boolean> theFuture : back2) {
Assert.assertTrue(theFuture.get());
}
+
+ emitter.verifyEmitted("query/time", 3);
+ Map<Integer, Long> codeFrequencies =
emitter.getMetricEvents("query/time").stream()
+ .map(ServiceMetricEvent::toMap)
+ .map(map -> (int)
map.get(DruidMetrics.CODE))
+ .collect(Collectors.groupingBy(
+ code -> code,
+ Collectors.counting()
+ ));
+ Assert.assertEquals(Map.of(200, 2L, 429, 1L), codeFrequencies);
}
@Test(timeout = 10_000L)
@@ -1618,6 +1687,15 @@ public class QueryResourceTest
for (Future<Boolean> theFuture : back2) {
Assert.assertTrue(theFuture.get());
}
+ emitter.verifyEmitted("query/time", 3);
+ Map<Integer, Long> codeFrequencies =
emitter.getMetricEvents("query/time").stream()
+ .map(ServiceMetricEvent::toMap)
+ .map(map -> (int)
map.get(DruidMetrics.CODE))
+ .collect(Collectors.groupingBy(
+ code -> code,
+ Collectors.counting()
+ ));
+ Assert.assertEquals(Map.of(200, 2L, 429, 1L), codeFrequencies);
}
@Test
@@ -1706,7 +1784,7 @@ public class QueryResourceTest
CONGLOMERATE,
texasRanger,
new DefaultGenericQueryMetricsFactory(),
- new NoopServiceEmitter(),
+ emitter,
testRequestLogger,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
diff --git
a/services/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java
b/services/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java
index 0fa20e24227..829f973fdac 100644
---
a/services/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java
+++
b/services/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java
@@ -766,7 +766,7 @@ public class AsyncQueryForwardingServlet extends
AsyncProxyServlet implements Qu
} else {
failedQueryCount.incrementAndGet();
}
- emitQueryTime(requestTimeNs, success, sqlQueryId, queryId);
+ emitQueryTime(requestTimeNs, success, sqlQueryId, queryId,
result.getFailure());
AuthenticationResult authenticationResult =
AuthorizationUtils.authenticationResultFromRequest(req);
@@ -853,7 +853,7 @@ public class AsyncQueryForwardingServlet extends
AsyncProxyServlet implements Qu
}
failedQueryCount.incrementAndGet();
- emitQueryTime(requestTimeNs, false, sqlQueryId, queryId);
+ emitQueryTime(requestTimeNs, false, sqlQueryId, queryId, failure);
AuthenticationResult authenticationResult =
AuthorizationUtils.authenticationResultFromRequest(req);
//noinspection VariableNotUsedInsideIf
@@ -929,7 +929,8 @@ public class AsyncQueryForwardingServlet extends
AsyncProxyServlet implements Qu
long requestTimeNs,
boolean success,
@Nullable String sqlQueryId,
- @Nullable String queryId
+ @Nullable String queryId,
+ Throwable queryException
)
{
QueryMetrics queryMetrics;
@@ -950,6 +951,7 @@ public class AsyncQueryForwardingServlet extends
AsyncProxyServlet implements Qu
);
}
queryMetrics.success(success);
+ queryMetrics.code(queryException);
queryMetrics.reportQueryTime(requestTimeNs).emit(emitter);
}
}
diff --git
a/services/src/test/java/org/apache/druid/server/AsyncQueryForwardingServletTest.java
b/services/src/test/java/org/apache/druid/server/AsyncQueryForwardingServletTest.java
index 2e6e2b3f593..2bd6c5107a3 100644
---
a/services/src/test/java/org/apache/druid/server/AsyncQueryForwardingServletTest.java
+++
b/services/src/test/java/org/apache/druid/server/AsyncQueryForwardingServletTest.java
@@ -51,6 +51,7 @@ import org.apache.druid.java.util.common.jackson.JacksonUtils;
import org.apache.druid.java.util.common.lifecycle.Lifecycle;
import org.apache.druid.java.util.metrics.StubServiceEmitter;
import org.apache.druid.query.DefaultGenericQueryMetricsFactory;
+import org.apache.druid.query.DruidMetrics;
import org.apache.druid.query.Druids;
import org.apache.druid.query.MapQueryToolChestWarehouse;
import org.apache.druid.query.Query;
@@ -719,11 +720,15 @@ public class AsyncQueryForwardingServletTest extends
BaseJettyTest
}
catch (NullPointerException ignored) {
}
- // Assert.assertEquals("query/time",
stubServiceEmitter.getEvents().get(0).toMap().get("metric"));
stubServiceEmitter.verifyEmitted("query/time", 1);
if (!isJDBCSql) {
Assert.assertEquals("dummy",
stubServiceEmitter.getEvents().get(0).toMap().get("id"));
}
+ if (isFailure) {
+ Assert.assertEquals(500,
stubServiceEmitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
+ } else {
+ Assert.assertEquals(200,
stubServiceEmitter.getMetricEvents("query/time").get(0).toMap().get(DruidMetrics.CODE));
+ }
// This test is mostly about verifying that the servlet calls the right
methods the right number of times.
EasyMock.verify(hostFinder, requestMock);
diff --git a/sql/src/main/java/org/apache/druid/sql/SqlExecutionReporter.java
b/sql/src/main/java/org/apache/druid/sql/SqlExecutionReporter.java
index f531144b5f0..74a52345f8a 100644
--- a/sql/src/main/java/org/apache/druid/sql/SqlExecutionReporter.java
+++ b/sql/src/main/java/org/apache/druid/sql/SqlExecutionReporter.java
@@ -24,6 +24,7 @@ import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.java.util.emitter.service.ServiceEmitter;
import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
+import org.apache.druid.query.DruidMetrics;
import org.apache.druid.query.QueryInterruptedException;
import org.apache.druid.query.QueryTimeoutException;
import org.apache.druid.server.QueryStats;
@@ -112,6 +113,10 @@ public class SqlExecutionReporter
}
metricBuilder.setDimension("remoteAddress",
StringUtils.nullToEmptyNonDruidDataString(remoteAddress));
metricBuilder.setDimension("success", String.valueOf(success));
+
+ final int statusCode = DruidMetrics.computeStatusCode(e);
+ metricBuilder.setDimension(DruidMetrics.CODE, statusCode);
+
emitter.emit(metricBuilder.setMetric("sqlQuery/time",
TimeUnit.NANOSECONDS.toMillis(queryTimeNs)));
if (bytesWritten >= 0) {
emitter.emit(metricBuilder.setMetric("sqlQuery/bytes", bytesWritten));
@@ -128,6 +133,7 @@ public class SqlExecutionReporter
statsMap.put("sqlQuery/planningTimeMs",
TimeUnit.NANOSECONDS.toMillis(planningTimeNanos));
statsMap.put("sqlQuery/bytes", bytesWritten);
statsMap.put("success", success);
+ statsMap.put(DruidMetrics.CODE, statusCode);
Map<String, Object> queryContext = stmt.queryContext;
if (plannerContext != null) {
statsMap.put("identity",
plannerContext.getAuthenticationResult().getIdentity());
diff --git a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
index 799cf1cc79f..a808d0e4633 100644
--- a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
@@ -54,6 +54,7 @@ import org.apache.druid.math.expr.ExprMacroTable;
import org.apache.druid.query.BadQueryContextException;
import org.apache.druid.query.BaseQuery;
import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.DruidMetrics;
import org.apache.druid.query.Query;
import org.apache.druid.query.QueryCapacityExceededException;
import org.apache.druid.query.QueryContexts;
@@ -404,6 +405,8 @@ public class SqlResourceTest extends CalciteTestBase
);
checkSqlRequestLog(true);
Assert.assertTrue(lifecycleManager.getAll("id").isEmpty());
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(200,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -491,6 +494,7 @@ public class SqlResourceTest extends CalciteTestBase
stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
stubServiceEmitter.verifyValue("sqlQuery/bytes", 27L);
stubServiceEmitter.verifyEmitted("sqlQuery/planningTimeMs", 1);
+ Assert.assertEquals(200,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
@@ -1542,6 +1546,8 @@ public class SqlResourceTest extends CalciteTestBase
);
checkSqlRequestLog(false);
Assert.assertTrue(lifecycleManager.getAll("id").isEmpty());
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(400,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -1563,6 +1569,8 @@ public class SqlResourceTest extends CalciteTestBase
);
checkSqlRequestLog(false);
Assert.assertTrue(lifecycleManager.getAll("id").isEmpty());
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(400,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
/**
@@ -1585,6 +1593,8 @@ public class SqlResourceTest extends CalciteTestBase
);
checkSqlRequestLog(false);
Assert.assertTrue(lifecycleManager.getAll("id").isEmpty());
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(400,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -1776,6 +1786,8 @@ public class SqlResourceTest extends CalciteTestBase
.expectMessageIs("Calcite assertion violated: [not a literal:
assertion_error()]")
);
Assert.assertTrue(lifecycleManager.getAll("id").isEmpty());
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(400,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -1872,6 +1884,15 @@ public class SqlResourceTest extends CalciteTestBase
Assert.assertEquals(1, limited);
Assert.assertEquals(3, testRequestLogger.getSqlQueryLogs().size());
Assert.assertTrue(lifecycleManager.getAll(sqlQueryId).isEmpty());
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 3);
+ Map<Integer, Long> codeFrequencies =
stubServiceEmitter.getMetricEvents("sqlQuery/time").stream()
+ .map(event ->
event.toMap())
+ .map(map -> (int)
map.get(DruidMetrics.CODE))
+
.collect(Collectors.groupingBy(
+ code -> code,
+
Collectors.counting()
+ ));
+ Assert.assertEquals(Map.of(200, 2L, 429, 1L), codeFrequencies);
}
@Test
@@ -1905,6 +1926,8 @@ public class SqlResourceTest extends CalciteTestBase
""
);
Assert.assertTrue(lifecycleManager.getAll(sqlQueryId).isEmpty());
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(504,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -1940,6 +1963,8 @@ public class SqlResourceTest extends CalciteTestBase
null,
""
);
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(500,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -1968,6 +1993,8 @@ public class SqlResourceTest extends CalciteTestBase
ErrorResponse exception = deserializeResponse(queryResponse,
ErrorResponse.class);
validateLegacyQueryExceptionErrorResponse(exception, "Query cancelled",
null, "");
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(500,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -2050,6 +2077,8 @@ public class SqlResourceTest extends CalciteTestBase
);
checkSqlRequestLog(false);
Assert.assertTrue(lifecycleManager.getAll(sqlQueryId).isEmpty());
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(400,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
@Test
@@ -2066,6 +2095,8 @@ public class SqlResourceTest extends CalciteTestBase
"Query context parameter [sqlInsertSegmentGranularity] is not allowed"
);
checkSqlRequestLog(false);
+ stubServiceEmitter.verifyEmitted("sqlQuery/time", 1);
+ Assert.assertEquals(400,
stubServiceEmitter.getMetricEvents("sqlQuery/time").get(0).toMap().get(DruidMetrics.CODE));
}
private void checkSqlRequestLog(boolean success)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]