This is an automated email from the ASF dual-hosted git repository.
jtuglu1 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 4e517615b82 feat: add query/segments/count metric to data nodes
(#19624)
4e517615b82 is described below
commit 4e517615b827eec7f7f2c4cce67e1f3c78685a3b
Author: jtuglu1 <[email protected]>
AuthorDate: Mon Jun 29 17:27:17 2026 -0700
feat: add query/segments/count metric to data nodes (#19624)
Allows for quick queries to understand how many segments a given query
issued scans for against a particular data node. Better than counting the
unique query/segment/time or similar, especially if you are sampling higher
volume metrics like query/segment/time, but still want to have quick way to
know stats about # of segments queried per node.
---
docs/operations/metrics.md | 4 +-
.../src/main/resources/defaultWhiteListMap.json | 4 +
.../main/resources/defaultMetricDimensions.json | 9 +-
.../src/main/resources/defaultWhiteListMap.json | 4 +
.../src/main/resources/defaultMetrics.json | 1 +
.../main/resources/defaultMetricDimensions.json | 1 +
.../apache/druid/query/DefaultQueryMetrics.java | 1 +
.../java/org/apache/druid/query/QueryMetrics.java | 11 +-
.../resources/loggingEmitterAllowedMetrics.json | 3 +-
.../resources/loggingEmitterAllowedMetrics.json | 1 +
.../appenderator/SinkQuerySegmentWalker.java | 56 ++++++++-
.../org/apache/druid/server/ServerManager.java | 1 +
.../apache/druid/query/EmittingQueryMetrics.java | 134 +++++++++++++++++++++
.../timeseries/EmittingTimeseriesQueryMetrics.java | 79 ++++++++++++
.../appenderator/StreamAppenderatorTest.java | 76 +++++++++++-
.../appenderator/StreamAppenderatorTester.java | 11 +-
.../server/coordination/ServerManagerTest.java | 27 ++++-
17 files changed, 409 insertions(+), 14 deletions(-)
diff --git a/docs/operations/metrics.md b/docs/operations/metrics.md
index 8fa59a1a55f..a3390f86a2a 100644
--- a/docs/operations/metrics.md
+++ b/docs/operations/metrics.md
@@ -64,7 +64,7 @@ Most metric values reset each emission period, as specified
in `druid.monitoring
|`query/failed/count`|Number of failed queries.|This metric is only available
if the `QueryCountStatsMonitor` module is included.| |
|`query/interrupted/count`|Number of queries interrupted due to
cancellation.|This metric is only available if the `QueryCountStatsMonitor`
module is included.| |
|`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/segments/count`|Number of segments that will be touched by the query.
The Broker 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. The Broker
will resend the query to the new servers that serve those segments after move.
In this case, those segments can be [...]
|`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`,
`statusCode`|< 1s|
|`sqlQuery/planningTimeMs`|Milliseconds taken to plan a SQL to native
query.|`id`, `nativeQueryIds`, `dataSource`, `remoteAddress`, `success`,
`engine`| |
@@ -107,6 +107,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.|<p>Common: `dataSource`,
`type`, `interval`, `hasFilters`, `duration`, `context`, `remoteAddress`, `id`,
`statusCode`.</p><p> Aggregation Queries: `numMetrics`,
`numComplexMetrics`.</p><p> GroupBy: `numDimensions`.</p><p> TopN: `threshold`,
`dimension`.</p>|< 1s|
+|`query/segments/count`|Number of segments this Historical scans for a
query.|<p>Common: `dataSource`, `type`, `interval`, `hasFilters`, `duration`,
`context`, `remoteAddress`, `id`.</p><p> Aggregation Queries: `numMetrics`,
`numComplexMetrics`.</p><p> GroupBy: `numDimensions`.</p><p> TopN: `threshold`,
`dimension`.</p>|Varies|
|`query/segment/time`|Milliseconds taken to query individual segment. Includes
time to page in the segment from disk.|`id`, `status`, `segment`,
`vectorized`.|several hundred milliseconds|
|`query/wait/time`|Milliseconds spent waiting for a segment to be
scanned.|`id`, `segment`|< several hundred milliseconds|
|`segment/scan/pending`|Number of segments in queue waiting to be
scanned.||Close to 0|
@@ -141,6 +142,7 @@ to represent the task ID are deprecated and will be removed
in a future release.
|Metric|Description|Dimensions|Normal value|
|------|-----------|----------|------------|
|`query/time`|Milliseconds taken to complete a query.|<p>Common: `dataSource`,
`type`, `interval`, `hasFilters`, `duration`, `context`, `remoteAddress`, `id`,
`statusCode`.</p><p> Aggregation Queries: `numMetrics`,
`numComplexMetrics`.</p><p> GroupBy: `numDimensions`.</p><p> TopN: `threshold`,
`dimension`.</p>|< 1s|
+|`query/segments/count`|Number of segments this Peon scans for a
query.|<p>Common: `dataSource`, `type`, `interval`, `hasFilters`, `duration`,
`context`, `remoteAddress`, `id`.</p><p> Aggregation Queries: `numMetrics`,
`numComplexMetrics`.</p><p> GroupBy: `numDimensions`. </p><p>TopN: `threshold`,
`dimension`.</p>|Varies|
|`query/wait/time`|Milliseconds spent waiting for a segment to be
scanned.|`id`, `segment`|several hundred milliseconds|
|`segment/scan/pending`|Number of segments in queue waiting to be
scanned.||Close to 0|
|`segment/scan/active`|Number of segments currently scanned. This metric also
indicates how many threads from `druid.processing.numThreads` are currently
being used.||Close to `druid.processing.numThreads`|
diff --git
a/extensions-contrib/ambari-metrics-emitter/src/main/resources/defaultWhiteListMap.json
b/extensions-contrib/ambari-metrics-emitter/src/main/resources/defaultWhiteListMap.json
index 8ac86ed46fc..88dee4c11cc 100644
---
a/extensions-contrib/ambari-metrics-emitter/src/main/resources/defaultWhiteListMap.json
+++
b/extensions-contrib/ambari-metrics-emitter/src/main/resources/defaultWhiteListMap.json
@@ -34,6 +34,10 @@
"dataSource",
"type"
],
+ "query/segments/count": [
+ "dataSource",
+ "type"
+ ],
"query/segment/time": [
"dataSource",
"type"
diff --git
a/extensions-contrib/dropwizard-emitter/src/main/resources/defaultMetricDimensions.json
b/extensions-contrib/dropwizard-emitter/src/main/resources/defaultMetricDimensions.json
index bc0c451870f..3a681d25cd9 100644
---
a/extensions-contrib/dropwizard-emitter/src/main/resources/defaultMetricDimensions.json
+++
b/extensions-contrib/dropwizard-emitter/src/main/resources/defaultMetricDimensions.json
@@ -21,6 +21,13 @@
"type": "timer",
"timeUnit": "MILLISECONDS"
},
+ "query/segments/count": {
+ "dimensions": [
+ "dataSource",
+ "type"
+ ],
+ "type": "counter"
+ },
"query/segment/time": {
"dimensions": [],
"type": "timer",
@@ -515,4 +522,4 @@
],
"type": "gauge"
}
-}
\ No newline at end of file
+}
diff --git
a/extensions-contrib/graphite-emitter/src/main/resources/defaultWhiteListMap.json
b/extensions-contrib/graphite-emitter/src/main/resources/defaultWhiteListMap.json
index 44bd5ef8db9..e0713e3663c 100644
---
a/extensions-contrib/graphite-emitter/src/main/resources/defaultWhiteListMap.json
+++
b/extensions-contrib/graphite-emitter/src/main/resources/defaultWhiteListMap.json
@@ -21,6 +21,10 @@
"dataSource",
"type"
],
+ "query/segments/count": [
+ "dataSource",
+ "type"
+ ],
"query/segment/time": [
"dataSource",
"type"
diff --git
a/extensions-contrib/prometheus-emitter/src/main/resources/defaultMetrics.json
b/extensions-contrib/prometheus-emitter/src/main/resources/defaultMetrics.json
index bbb4d7b11a5..e582958e768 100644
---
a/extensions-contrib/prometheus-emitter/src/main/resources/defaultMetrics.json
+++
b/extensions-contrib/prometheus-emitter/src/main/resources/defaultMetrics.json
@@ -28,6 +28,7 @@
"schemacache/inTransitSMQPublishedResults/count" : { "dimensions" : [],
"type" : "count", "help": "Number of segments for which schema is cached after
back filling in the database."},
"serverview/sync/healthy" : { "dimensions" : ["server"], "type" : "gauge",
"help": "Sync status of the Broker with a segment-loading server such as a
Historical or Peon."},
"serverview/sync/unstableTime" : { "dimensions" : ["server"], "type" :
"timer", "conversionFactor": 1000.0, "help": "Time in seconds for which the
Broker has been failing to sync with a segment-loading server."},
+ "query/segments/count" : { "dimensions" : ["dataSource", "type"], "type" :
"count", "help": "Number of segments this data node scans for a query."},
"query/segment/time" : { "dimensions" : [], "type" : "timer",
"conversionFactor": 1000.0, "help": "Seconds taken to query individual segment.
Includes time to page in the segment from disk."},
"query/wait/time" : { "dimensions" : [], "type" : "timer",
"conversionFactor": 1000.0, "help": "Seconds spent waiting for a segment to be
scanned."},
"segment/scan/pending" : { "dimensions" : [], "type" : "gauge", "help":
"Number of segments in queue waiting to be scanned."},
diff --git
a/extensions-contrib/statsd-emitter/src/main/resources/defaultMetricDimensions.json
b/extensions-contrib/statsd-emitter/src/main/resources/defaultMetricDimensions.json
index 828031cfb11..9eae1dd5a62 100644
---
a/extensions-contrib/statsd-emitter/src/main/resources/defaultMetricDimensions.json
+++
b/extensions-contrib/statsd-emitter/src/main/resources/defaultMetricDimensions.json
@@ -5,6 +5,7 @@
"query/node/ttfb" : { "dimensions" : ["server"], "type" : "timer"},
"query/node/bytes" : { "dimensions" : ["server"], "type" : "count"},
+ "query/segments/count" : { "dimensions" : ["dataSource", "type"], "type" :
"count"},
"query/segment/time" : { "dimensions" : [], "type" : "timer"},
"query/wait/time" : { "dimensions" : [], "type" : "timer"},
"segment/scan/pending" : { "dimensions" : [], "type" : "gauge"},
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 f3a4539f780..cc994f72a18 100644
--- a/processing/src/main/java/org/apache/druid/query/DefaultQueryMetrics.java
+++ b/processing/src/main/java/org/apache/druid/query/DefaultQueryMetrics.java
@@ -46,6 +46,7 @@ public class DefaultQueryMetrics<QueryType extends Query<?>>
implements QueryMet
public static final String QUERY_BYTES = "query/bytes";
public static final String QUERY_CPU_TIME = "query/cpu/time";
public static final String QUERY_WAIT_TIME = "query/wait/time";
+ public static final String QUERY_SEGMENTS_COUNT = "query/segments/count";
public static final String QUERY_SEGMENT_TIME = "query/segment/time";
public static final String QUERY_SEGMENT_AND_CACHE_TIME =
"query/segmentAndCache/time";
public static final String QUERY_RESULT_CACHE_HIT = "query/resultCache/hit";
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 ebe99476062..a6a14846be5 100644
--- a/processing/src/main/java/org/apache/druid/query/QueryMetrics.java
+++ b/processing/src/main/java/org/apache/druid/query/QueryMetrics.java
@@ -329,7 +329,16 @@ public interface QueryMetrics<QueryType extends Query<?>>
QueryMetrics<QueryType> reportQueryBytes(long byteCount);
/**
- * Registers "segments queried count" metric.
+ * Registers the {@code query/segments/count} metric, the number of segments
touched by the query.
+ *
+ * Emitted once per query. The meaning of the metric depends on the emitting
process:
+ * <ul>
+ * <li>On the Broker, it is the number of segments in the planned query
distribution (a snapshot-based
+ * count that may double-count segments that moved and were
re-fetched).</li>
+ * <li>On a data node (Historical, Peon/realtime), it is the number of
segments that node actually scanned
+ * for the query.</li>
+ * </ul>
+ * The two are disambiguated by the emitting service/host, not by the metric
name.
*/
QueryMetrics<QueryType> reportQueriedSegmentCount(long segmentCount);
diff --git a/processing/src/main/resources/loggingEmitterAllowedMetrics.json
b/processing/src/main/resources/loggingEmitterAllowedMetrics.json
index 6349c04b5dd..9bcdb712952 100644
--- a/processing/src/main/resources/loggingEmitterAllowedMetrics.json
+++ b/processing/src/main/resources/loggingEmitterAllowedMetrics.json
@@ -134,6 +134,7 @@
"query/node/bytes": [],
"query/node/time": [],
"query/node/ttfb": [],
+ "query/segments/count": [],
"query/segment/time": [],
"query/segmentAndCache/time": [],
"query/success/count": [],
@@ -249,4 +250,4 @@
"tier/total/capacity": [],
"zk/connected": [],
"zk/reconnect/time": []
-}
\ No newline at end of file
+}
diff --git a/processing/src/test/resources/loggingEmitterAllowedMetrics.json
b/processing/src/test/resources/loggingEmitterAllowedMetrics.json
index 06ff68822b3..3d3200362f0 100644
--- a/processing/src/test/resources/loggingEmitterAllowedMetrics.json
+++ b/processing/src/test/resources/loggingEmitterAllowedMetrics.json
@@ -134,6 +134,7 @@
"query/node/bytes": [],
"query/node/time": [],
"query/node/ttfb": [],
+ "query/segments/count": [],
"query/segment/time": [],
"query/segmentAndCache/time": [],
"query/success/count": [],
diff --git
a/server/src/main/java/org/apache/druid/segment/realtime/appenderator/SinkQuerySegmentWalker.java
b/server/src/main/java/org/apache/druid/segment/realtime/appenderator/SinkQuerySegmentWalker.java
index 2b5a6210b2c..b50939c85cd 100644
---
a/server/src/main/java/org/apache/druid/segment/realtime/appenderator/SinkQuerySegmentWalker.java
+++
b/server/src/main/java/org/apache/druid/segment/realtime/appenderator/SinkQuerySegmentWalker.java
@@ -82,6 +82,7 @@ import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -210,6 +211,9 @@ public class SinkQuerySegmentWalker implements
QuerySegmentWalker
// QueryRunner returned by this method is closed. (We can't do the
acquisition and releasing at the level of
// each FireHydrant's runner, since then it wouldn't be properly
all-or-nothing on a per-Sink basis.)
final List<SinkSegmentReference> allSegmentReferences = new ArrayList<>();
+ // Distinct Sinks (Druid segments) actually queried by this node. Tracked
separately from
+ // allSegmentReferences, which holds one reference per FireHydrant
(subsegment) and would overcount.
+ final Set<SegmentId> queriedSinks = new HashSet<>();
final Map<SegmentDescriptor, SegmentId> segmentIdMap = new HashMap<>();
final LinkedHashMap<SegmentDescriptor, List<QueryRunner<T>>> allRunners =
new LinkedHashMap<>();
final ConcurrentHashMap<String,
SinkMetricsEmittingQueryRunner.SegmentMetrics> segmentMetricsAccumulator = new
ConcurrentHashMap<>();
@@ -241,6 +245,7 @@ public class SinkQuerySegmentWalker implements
QuerySegmentWalker
allRunners.put(descriptor, Collections.singletonList(new
NoopQueryRunner<>()));
} else {
allSegmentReferences.addAll(sinkSegmentReferences);
+ queriedSinks.add(sinkSegmentId);
allRunners.put(
descriptor,
@@ -354,6 +359,8 @@ public class SinkQuerySegmentWalker implements
QuerySegmentWalker
);
}
+ final int segmentCount = queriedSinks.size();
+
// 1) Populate resource id to the query
// 2) Merge results using the toolChest, finalize if necessary.
// 3) Measure CPU time of that operation.
@@ -370,7 +377,8 @@ public class SinkQuerySegmentWalker implements
QuerySegmentWalker
),
segmentMetricsAccumulator,
Collections.emptySet(),
- null
+ null,
+ segmentCount
),
toolChest,
emitter,
@@ -452,12 +460,15 @@ public class SinkQuerySegmentWalker implements
QuerySegmentWalker
}
/**
- * This class is responsible for emitting query/segment/time,
query/wait/time and query/segmentAndCache/Time metrics for a Sink.
+ * This class is responsible for emitting query/segment/time,
query/wait/time, query/segmentAndCache/Time and
+ * query/segments/count metrics for a Sink.
* It accumulates query/segment/time and query/segmentAndCache/time metric
for each FireHydrant at the level of Sink.
* query/wait/time metric is the time taken to process the first FireHydrant
for the Sink.
* <p>
* This class operates in two distinct modes based on whether {@link
SinkMetricsEmittingQueryRunner#segmentId} is null or non-null.
- * When segmentId is non-null, it accumulates the metrics. When segmentId is
null, it emits the accumulated metrics.
+ * When segmentId is non-null, it accumulates the per-segment metrics. When
segmentId is null, it emits the accumulated
+ * per-segment metrics. Unlike those, query/segments/count is a single
per-query total (the number of Sinks scanned by
+ * this node), so it is emitted exactly once by the segmentId-null runner
that receives a non-null {@code segmentCount}.
* <p>
* This class is derived from {@link
org.apache.druid.query.MetricsEmittingQueryRunner}.
*/
@@ -470,6 +481,8 @@ public class SinkQuerySegmentWalker implements
QuerySegmentWalker
private final Set<String> metricsToCompute;
@Nullable
private final String segmentId;
+ @Nullable
+ private final Integer segmentCount;
private final long creationTimeNs;
private SinkMetricsEmittingQueryRunner(
@@ -480,6 +493,27 @@ public class SinkQuerySegmentWalker implements
QuerySegmentWalker
Set<String> metricsToCompute,
@Nullable String segmentId
)
+ {
+ this(
+ emitter,
+ queryToolChest,
+ queryRunner,
+ segmentMetricsAccumulator,
+ metricsToCompute,
+ segmentId,
+ null
+ );
+ }
+
+ private SinkMetricsEmittingQueryRunner(
+ ServiceEmitter emitter,
+ QueryToolChest<T, ? extends Query<T>> queryToolChest,
+ QueryRunner<T> queryRunner,
+ ConcurrentHashMap<String, SegmentMetrics> segmentMetricsAccumulator,
+ Set<String> metricsToCompute,
+ @Nullable String segmentId,
+ @Nullable Integer segmentCount
+ )
{
this.emitter = emitter;
this.queryToolChest = queryToolChest;
@@ -487,6 +521,7 @@ public class SinkQuerySegmentWalker implements
QuerySegmentWalker
this.segmentMetricsAccumulator = segmentMetricsAccumulator;
this.metricsToCompute = metricsToCompute;
this.segmentId = segmentId;
+ this.segmentCount = segmentCount;
this.creationTimeNs = System.nanoTime();
}
@@ -544,6 +579,21 @@ public class SinkQuerySegmentWalker implements
QuerySegmentWalker
log.error(e, "Failed to emit metrics for segment[%s]",
segmentAndMetrics.getKey());
}
}
+
+ // Emit total segmentCount once per query
+ if (segmentCount != null) {
+ final QueryMetrics<?> segmentCountMetrics =
+
queryPlus.withoutThreadUnsafeState().withQueryMetrics(queryToolChest).getQueryMetrics();
+ segmentCountMetrics.reportQueriedSegmentCount(segmentCount);
+
+ try {
+ segmentCountMetrics.emit(emitter);
+ }
+ catch (Exception e) {
+ // Query should not fail, because of emitter failure.
Swallowing the exception.
+ log.error(e, "Failed to emit queried segment count
metric.");
+ }
+ }
}
}
}
diff --git a/server/src/main/java/org/apache/druid/server/ServerManager.java
b/server/src/main/java/org/apache/druid/server/ServerManager.java
index 60e854f0f6c..1e085be7854 100644
--- a/server/src/main/java/org/apache/druid/server/ServerManager.java
+++ b/server/src/main/java/org/apache/druid/server/ServerManager.java
@@ -644,6 +644,7 @@ public class ServerManager implements QuerySegmentWalker
segmentMapFn
);
closer.registerAll(segmentReferences);
+
queryPlus.getQueryMetrics().reportQueriedSegmentCount(segmentReferences.size()).emit(emitter);
final FunctionalIterable<QueryRunner<T>> queryRunners =
getQueryRunnersForSegments(
query,
diff --git
a/server/src/test/java/org/apache/druid/query/EmittingQueryMetrics.java
b/server/src/test/java/org/apache/druid/query/EmittingQueryMetrics.java
new file mode 100644
index 00000000000..a0996b77723
--- /dev/null
+++ b/server/src/test/java/org/apache/druid/query/EmittingQueryMetrics.java
@@ -0,0 +1,134 @@
+/*
+ * 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 java.util.concurrent.TimeUnit;
+
+/**
+ * Test-only {@link QueryMetrics} that emits every metric {@link
DefaultQueryMetrics} disables by default.
+ * Production keeps those methods as no-ops, but tests that need to assert on
any of them can use this implementation
+ * (or {@link Factory}) instead.
+ */
+public class EmittingQueryMetrics<QueryType extends Query<?>> extends
DefaultQueryMetrics<QueryType>
+{
+ @Override
+ public QueryMetrics<QueryType> reportBackPressureTime(long timeNs)
+ {
+ return reportMetric("query/node/backpressure",
TimeUnit.NANOSECONDS.toMillis(timeNs));
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportBitmapConstructionTime(long timeNs)
+ {
+ return reportMetric("query/segment/bitmap/constructionTime",
TimeUnit.NANOSECONDS.toMillis(timeNs));
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportSegmentRows(long numRows)
+ {
+ return reportMetric("query/segment/rows", numRows);
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportPreFilteredRows(long numRows)
+ {
+ return reportMetric("query/segment/preFilteredRows", numRows);
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportParallelMergeParallelism(int
parallelism)
+ {
+ return reportMetric("query/parallelMerge/parallelism", parallelism);
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportParallelMergeInputSequences(long
numSequences)
+ {
+ return reportMetric("query/parallelMerge/inputSequences", numSequences);
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportParallelMergeInputRows(long numRows)
+ {
+ return reportMetric("query/parallelMerge/inputRows", numRows);
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportParallelMergeOutputRows(long numRows)
+ {
+ return reportMetric("query/parallelMerge/outputRows", numRows);
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportParallelMergeTaskCount(long numTasks)
+ {
+ return reportMetric("query/parallelMerge/taskCount", numTasks);
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportParallelMergeTotalCpuTime(long timeNs)
+ {
+ return reportMetric("query/parallelMerge/cpuTime",
TimeUnit.NANOSECONDS.toMicros(timeNs));
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportParallelMergeTotalTime(long timeNs)
+ {
+ return reportMetric("query/parallelMerge/time",
TimeUnit.NANOSECONDS.toMillis(timeNs));
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportParallelMergeFastestPartitionTime(long
timeNs)
+ {
+ return reportMetric("query/parallelMerge/fastestPartitionTime",
TimeUnit.NANOSECONDS.toMillis(timeNs));
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportParallelMergeSlowestPartitionTime(long
timeNs)
+ {
+ return reportMetric("query/parallelMerge/slowestPartitionTime",
TimeUnit.NANOSECONDS.toMillis(timeNs));
+ }
+
+ @Override
+ public QueryMetrics<QueryType> reportQueriedSegmentCount(long segmentCount)
+ {
+ return reportMetric(QUERY_SEGMENTS_COUNT, segmentCount);
+ }
+
+ /**
+ * {@link GenericQueryMetricsFactory} producing {@link EmittingQueryMetrics}.
+ */
+ public static class Factory implements GenericQueryMetricsFactory
+ {
+ @Override
+ public QueryMetrics<Query<?>> makeMetrics(Query<?> query)
+ {
+ final EmittingQueryMetrics<Query<?>> queryMetrics = new
EmittingQueryMetrics<>();
+ queryMetrics.query(query);
+ return queryMetrics;
+ }
+
+ @Override
+ public QueryMetrics<Query<?>> makeMetrics()
+ {
+ return new EmittingQueryMetrics<>();
+ }
+ }
+}
diff --git
a/server/src/test/java/org/apache/druid/query/timeseries/EmittingTimeseriesQueryMetrics.java
b/server/src/test/java/org/apache/druid/query/timeseries/EmittingTimeseriesQueryMetrics.java
new file mode 100644
index 00000000000..e2d7b861233
--- /dev/null
+++
b/server/src/test/java/org/apache/druid/query/timeseries/EmittingTimeseriesQueryMetrics.java
@@ -0,0 +1,79 @@
+/*
+ * 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.timeseries;
+
+import org.apache.druid.query.DruidMetrics;
+import org.apache.druid.query.EmittingQueryMetrics;
+
+/**
+ * {@link TimeseriesQueryMetrics} counterpart of {@link EmittingQueryMetrics}:
it inherits the emitting overrides for
+ * every metric {@code DefaultQueryMetrics} disables by default, and adds the
Timeseries-specific dimensions (mirroring
+ * {@link DefaultTimeseriesQueryMetrics}) so it can be used by {@link
TimeseriesQueryQueryToolChest} in tests.
+ */
+public class EmittingTimeseriesQueryMetrics extends
EmittingQueryMetrics<TimeseriesQuery>
+ implements TimeseriesQueryMetrics
+{
+ @Override
+ public void query(TimeseriesQuery query)
+ {
+ super.query(query);
+ limit(query);
+ numMetrics(query);
+ numComplexMetrics(query);
+ granularity(query);
+ }
+
+ @Override
+ public void limit(TimeseriesQuery query)
+ {
+ setDimension("limit", String.valueOf(query.getLimit()));
+ }
+
+ @Override
+ public void numMetrics(TimeseriesQuery query)
+ {
+ setDimension("numMetrics",
String.valueOf(query.getAggregatorSpecs().size()));
+ }
+
+ @Override
+ public void numComplexMetrics(TimeseriesQuery query)
+ {
+ int numComplexAggs =
DruidMetrics.findNumComplexAggs(query.getAggregatorSpecs());
+ setDimension("numComplexMetrics", String.valueOf(numComplexAggs));
+ }
+
+ @Override
+ public void granularity(TimeseriesQuery query)
+ {
+ // Don't emit by default
+ }
+
+ /**
+ * {@link TimeseriesQueryMetricsFactory} producing {@link
EmittingTimeseriesQueryMetrics}.
+ */
+ public static class Factory implements TimeseriesQueryMetricsFactory
+ {
+ @Override
+ public TimeseriesQueryMetrics makeMetrics()
+ {
+ return new EmittingTimeseriesQueryMetrics();
+ }
+ }
+}
diff --git
a/server/src/test/java/org/apache/druid/segment/realtime/appenderator/StreamAppenderatorTest.java
b/server/src/test/java/org/apache/druid/segment/realtime/appenderator/StreamAppenderatorTest.java
index 904d99b7cde..bc5b3c6de36 100644
---
a/server/src/test/java/org/apache/druid/segment/realtime/appenderator/StreamAppenderatorTest.java
+++
b/server/src/test/java/org/apache/druid/segment/realtime/appenderator/StreamAppenderatorTest.java
@@ -2294,10 +2294,84 @@ public class StreamAppenderatorTest extends
InitializedNullHandlingTest
}
}
+ @Test
+ public void testQueryByIntervalsEmitsQueriedSegmentCountMetric() throws
Exception
+ {
+ try (
+ StubServiceEmitter serviceEmitter = new StubServiceEmitter();
+ final StreamAppenderatorTester tester =
+ new StreamAppenderatorTester.Builder().maxRowsInMemory(100)
+
.basePersistDirectory(temporaryFolder.newFolder())
+
.withServiceEmitter(serviceEmitter)
+ .build()) {
+ final Appenderator appenderator = tester.getAppenderator();
+
+ appenderator.startJob();
+ appenderator.add(IDENTIFIERS.get(0), ir("2000", "foo", 1),
Suppliers.ofInstance(Committers.nil()));
+ appenderator.add(IDENTIFIERS.get(1), ir("2000", "foo", 4),
Suppliers.ofInstance(Committers.nil()));
+
+ final TimeseriesQuery query = Druids.newTimeseriesQueryBuilder()
+
.dataSource(StreamAppenderatorTester.DATASOURCE)
+
.intervals(ImmutableList.of(Intervals.of("2000/2001")))
+ .aggregators(
+ Collections.singletonList(
+ new
LongSumAggregatorFactory("count", "count")
+ )
+ )
+ .granularity(Granularities.DAY)
+ .build();
+
+ QueryPlus.wrap(query).run(appenderator,
ResponseContext.createEmpty()).toList();
+
+ serviceEmitter.verifyEmitted(DefaultQueryMetrics.QUERY_SEGMENTS_COUNT,
1);
+ serviceEmitter.verifyValue(DefaultQueryMetrics.QUERY_SEGMENTS_COUNT, 2L);
+ }
+ }
+
+ @Test
+ public void testQueryEmitsQueriedSegmentCountCountsSinksNotHydrants() throws
Exception
+ {
+ try (
+ StubServiceEmitter serviceEmitter = new StubServiceEmitter();
+ final StreamAppenderatorTester tester =
+ new StreamAppenderatorTester.Builder().maxRowsInMemory(100)
+
.basePersistDirectory(temporaryFolder.newFolder())
+
.withServiceEmitter(serviceEmitter)
+ .build()) {
+ final Appenderator appenderator = tester.getAppenderator();
+
+ appenderator.startJob();
+ // Persisting between adds to the same identifier swaps the current
FireHydrant for a new one, so this single
+ // Sink ends up with multiple hydrants. query/segments/count must report
1 (the Sink/segment), not the hydrant count.
+ appenderator.add(IDENTIFIERS.get(0), ir("2000", "foo", 1),
Suppliers.ofInstance(Committers.nil()));
+ appenderator.persistAll(Committers.nil()).get();
+ appenderator.add(IDENTIFIERS.get(0), ir("2000", "bar", 2),
Suppliers.ofInstance(Committers.nil()));
+ appenderator.persistAll(Committers.nil()).get();
+ appenderator.add(IDENTIFIERS.get(0), ir("2000", "baz", 3),
Suppliers.ofInstance(Committers.nil()));
+
+ final TimeseriesQuery query = Druids.newTimeseriesQueryBuilder()
+
.dataSource(StreamAppenderatorTester.DATASOURCE)
+
.intervals(ImmutableList.of(Intervals.of("2000/2001")))
+ .aggregators(
+ Collections.singletonList(
+ new
LongSumAggregatorFactory("count", "count")
+ )
+ )
+ .granularity(Granularities.DAY)
+ .build();
+
+ QueryPlus.wrap(query).run(appenderator,
ResponseContext.createEmpty()).toList();
+
+ serviceEmitter.verifyEmitted(DefaultQueryMetrics.QUERY_SEGMENTS_COUNT,
1);
+ serviceEmitter.verifyValue(DefaultQueryMetrics.QUERY_SEGMENTS_COUNT, 1L);
+ }
+ }
+
private void verifySinkMetrics(StubServiceEmitter emitter, Set<String>
segmentIds)
{
- int segments = segmentIds.size();
+ final int segments = segmentIds.size();
emitter.verifyEmitted(DefaultQueryMetrics.QUERY_CPU_TIME, 1);
+ emitter.verifyEmitted(DefaultQueryMetrics.QUERY_SEGMENTS_COUNT, 1);
Assert.assertEquals(segments,
emitter.getMetricEvents(DefaultQueryMetrics.QUERY_SEGMENT_TIME).size());
Assert.assertEquals(segments,
emitter.getMetricEvents(DefaultQueryMetrics.QUERY_SEGMENT_AND_CACHE_TIME).size());
Assert.assertEquals(segments,
emitter.getMetricEvents(DefaultQueryMetrics.QUERY_WAIT_TIME).size());
diff --git
a/server/src/test/java/org/apache/druid/segment/realtime/appenderator/StreamAppenderatorTester.java
b/server/src/test/java/org/apache/druid/segment/realtime/appenderator/StreamAppenderatorTester.java
index 5d659199108..93571eba5ba 100644
---
a/server/src/test/java/org/apache/druid/segment/realtime/appenderator/StreamAppenderatorTester.java
+++
b/server/src/test/java/org/apache/druid/segment/realtime/appenderator/StreamAppenderatorTester.java
@@ -39,8 +39,8 @@ import org.apache.druid.java.util.emitter.EmittingLogger;
import org.apache.druid.java.util.emitter.core.NoopEmitter;
import org.apache.druid.java.util.emitter.service.ServiceEmitter;
import org.apache.druid.math.expr.ExprMacroTable;
-import org.apache.druid.query.DefaultGenericQueryMetricsFactory;
import org.apache.druid.query.DefaultQueryRunnerFactoryConglomerate;
+import org.apache.druid.query.EmittingQueryMetrics;
import org.apache.druid.query.ForwardingQueryProcessingPool;
import org.apache.druid.query.QueryRunnerTestHelper;
import org.apache.druid.query.aggregation.CountAggregatorFactory;
@@ -53,6 +53,7 @@ import org.apache.druid.query.scan.ScanQueryConfig;
import org.apache.druid.query.scan.ScanQueryEngine;
import org.apache.druid.query.scan.ScanQueryQueryToolChest;
import org.apache.druid.query.scan.ScanQueryRunnerFactory;
+import org.apache.druid.query.timeseries.EmittingTimeseriesQueryMetrics;
import org.apache.druid.query.timeseries.TimeseriesQuery;
import org.apache.druid.query.timeseries.TimeseriesQueryEngine;
import org.apache.druid.query.timeseries.TimeseriesQueryQueryToolChest;
@@ -209,12 +210,12 @@ public class StreamAppenderatorTester implements
AutoCloseable
indexMerger,
DefaultQueryRunnerFactoryConglomerate.buildFromQueryRunnerFactories(ImmutableMap.of(
TimeseriesQuery.class, new TimeseriesQueryRunnerFactory(
- new TimeseriesQueryQueryToolChest(),
+ new TimeseriesQueryQueryToolChest(new
EmittingTimeseriesQueryMetrics.Factory()),
new TimeseriesQueryEngine(),
QueryRunnerTestHelper.NOOP_QUERYWATCHER
),
ScanQuery.class, new ScanQueryRunnerFactory(
- new
ScanQueryQueryToolChest(DefaultGenericQueryMetricsFactory.instance()),
+ new ScanQueryQueryToolChest(new
EmittingQueryMetrics.Factory()),
new ScanQueryEngine(),
new ScanQueryConfig()
)
@@ -252,12 +253,12 @@ public class StreamAppenderatorTester implements
AutoCloseable
indexMerger,
DefaultQueryRunnerFactoryConglomerate.buildFromQueryRunnerFactories(ImmutableMap.of(
TimeseriesQuery.class, new TimeseriesQueryRunnerFactory(
- new TimeseriesQueryQueryToolChest(),
+ new TimeseriesQueryQueryToolChest(new
EmittingTimeseriesQueryMetrics.Factory()),
new TimeseriesQueryEngine(),
QueryRunnerTestHelper.NOOP_QUERYWATCHER
),
ScanQuery.class, new ScanQueryRunnerFactory(
- new
ScanQueryQueryToolChest(DefaultGenericQueryMetricsFactory.instance()),
+ new ScanQueryQueryToolChest(new
EmittingQueryMetrics.Factory()),
new ScanQueryEngine(),
new ScanQueryConfig()
)
diff --git
a/server/src/test/java/org/apache/druid/server/coordination/ServerManagerTest.java
b/server/src/test/java/org/apache/druid/server/coordination/ServerManagerTest.java
index 92b2dcfc8ab..f4dfa43e8e2 100644
---
a/server/src/test/java/org/apache/druid/server/coordination/ServerManagerTest.java
+++
b/server/src/test/java/org/apache/druid/server/coordination/ServerManagerTest.java
@@ -52,10 +52,12 @@ import
org.apache.druid.java.util.common.guava.YieldingAccumulator;
import org.apache.druid.java.util.common.guava.YieldingSequenceBase;
import org.apache.druid.java.util.emitter.EmittingLogger;
import org.apache.druid.java.util.emitter.service.ServiceEmitter;
+import org.apache.druid.java.util.metrics.StubServiceEmitter;
import org.apache.druid.query.DataSource;
import org.apache.druid.query.DefaultQueryMetrics;
import org.apache.druid.query.DefaultQueryRunnerFactoryConglomerate;
import org.apache.druid.query.Druids;
+import org.apache.druid.query.EmittingQueryMetrics;
import org.apache.druid.query.ForwardingQueryProcessingPool;
import org.apache.druid.query.NoopQueryRunner;
import org.apache.druid.query.Query;
@@ -482,6 +484,29 @@ public class ServerManagerTest
Assert.assertSame(NoopQueryRunner.class, queryRunner.getClass());
}
+ @Test
+ public void testGetQueryRunnerForIntervalsEmitsQueriedSegmentCountMetric()
+ {
+ final StubServiceEmitter stubServiceEmitter =
StubServiceEmitter.createStarted();
+ serviceEmitter = stubServiceEmitter;
+ factory = new MyQueryRunnerFactory(new CountDownLatch(0), new
CountDownLatch(0), new CountDownLatch(0));
+ conglomerate =
DefaultQueryRunnerFactoryConglomerate.buildFromQueryRunnerFactories(ImmutableMap.of(
+ SearchQuery.class,
+ factory
+ ));
+ serverManager =
Guice.createInjector(BoundFieldModule.of(this)).getInstance(ServerManager.class);
+
+ final Interval interval = Intervals.of("P2d/2011-04-02");
+ final SearchQuery query = searchQuery("test", interval, Granularities.DAY);
+
+ serverManager.getQueryRunnerForIntervals(query, ImmutableList.of(interval))
+ .run(QueryPlus.wrap(query))
+ .toList();
+
+ stubServiceEmitter.verifyEmitted(DefaultQueryMetrics.QUERY_SEGMENTS_COUNT,
1);
+ stubServiceEmitter.verifyValue(DefaultQueryMetrics.QUERY_SEGMENTS_COUNT,
2L);
+ }
+
@Test
public void
testGetQueryRunnerForSegments_whenTimelineIsMissingReportingMissingSegmentsOnQueryDataSource()
{
@@ -830,7 +855,7 @@ public class ServerManagerTest
@Override
public QueryMetrics<Query<?>> makeMetrics(QueryType query)
{
- return new DefaultQueryMetrics<>();
+ return new EmittingQueryMetrics<>();
}
@Override
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]