Skip to content

Commit f30c7b6

Browse files
authored
Adding profiling support for concurrent segment search (#14413)
--------- Signed-off-by: Ankit Jain <[email protected]>
1 parent bec05a1 commit f30c7b6

File tree

11 files changed

+513
-79
lines changed

11 files changed

+513
-79
lines changed

lucene/CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ New Features
137137
* GITHUB#14404: Introducing DocValuesMultiRangeQuery.SortedNumericStabbingBuilder into sandbox.
138138
(Mikhail Khludnev)
139139

140+
* GITHUB#14413: Adding profiling support for concurrent segment search. (Ankit Jain)
141+
140142
* GITHUB#14565: Add ParentsChildrenBlockJoinQuery that supports parent and child filter in the same query
141143
along with limiting number of child documents to retrieve per parent. (Jinny Wang)
142144

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.lucene.sandbox.search;
19+
20+
import java.util.Collections;
21+
import java.util.Map;
22+
import java.util.Objects;
23+
24+
/**
25+
* This class is the internal representation of a profiled Query, corresponding to a single node in
26+
* the query tree. It is built after the query has finished executing and is merely a structured
27+
* representation, rather than the entity that collects the timing profile.
28+
*/
29+
public class AggregatedQueryLeafProfilerResult {
30+
private final Thread thread;
31+
private final Map<String, Long> breakdown;
32+
private final long startTime;
33+
private final long totalTime;
34+
35+
public AggregatedQueryLeafProfilerResult(
36+
Thread thread, Map<String, Long> breakdown, long startTime, long totalTime) {
37+
this.thread = thread;
38+
this.breakdown =
39+
Collections.unmodifiableMap(
40+
Objects.requireNonNull(breakdown, "required breakdown argument missing"));
41+
this.startTime = startTime;
42+
this.totalTime = totalTime;
43+
}
44+
45+
/**
46+
* Returns the thread that executed query for these leaves
47+
*
48+
* @return thread that executed query node
49+
*/
50+
public Thread getThread() {
51+
return thread;
52+
}
53+
54+
/**
55+
* Returns the timing breakdown for this node.
56+
*
57+
* @return map containing time breakdown across different operation types
58+
*/
59+
public Map<String, Long> getTimeBreakdown() {
60+
return breakdown;
61+
}
62+
63+
/**
64+
* Returns the start time for this query node execution.
65+
*
66+
* @return start time in nanoseconds
67+
*/
68+
public long getStartTime() {
69+
return startTime;
70+
}
71+
72+
/**
73+
* Returns the total time (inclusive of children) for this query node.
74+
*
75+
* @return elapsed time in nanoseconds
76+
*/
77+
public long getTotalTime() {
78+
return totalTime;
79+
}
80+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.lucene.sandbox.search;
19+
20+
import java.util.Arrays;
21+
import java.util.Collection;
22+
import java.util.Collections;
23+
import java.util.EnumMap;
24+
import java.util.Map;
25+
import org.apache.lucene.util.CollectionUtil;
26+
27+
/**
28+
* A record of timings for the various operations that may happen during query execution. A node's
29+
* time may be composed of several internal attributes (rewriting, weighting, scoring, etc).
30+
*/
31+
public class QueryLeafProfilerBreakdown {
32+
private static final Collection<QueryProfilerTimingType> LEAF_LEVEL_TIMING_TYPE =
33+
Arrays.stream(QueryProfilerTimingType.values()).filter(t -> t.isLeafLevel()).toList();
34+
35+
/** The accumulated timings for this query node */
36+
private final QueryProfilerTimer[] timers;
37+
38+
EnumMap<QueryProfilerTimingType, QueryProfilerTimer> map;
39+
40+
/** Sole constructor. */
41+
public QueryLeafProfilerBreakdown() {
42+
timers = new QueryProfilerTimer[LEAF_LEVEL_TIMING_TYPE.size()];
43+
for (int i = 0; i < timers.length; ++i) {
44+
timers[i] = new QueryProfilerTimer();
45+
}
46+
}
47+
48+
public QueryProfilerTimer getTimer(QueryProfilerTimingType type) {
49+
return timers[type.ordinal()];
50+
}
51+
52+
/** Build a timing count breakdown. */
53+
public final Map<String, Long> toBreakdownMap() {
54+
Map<String, Long> map = CollectionUtil.newHashMap(timers.length * 2);
55+
for (QueryProfilerTimingType type : LEAF_LEVEL_TIMING_TYPE) {
56+
map.put(type.toString(), timers[type.ordinal()].getApproximateTiming());
57+
map.put(type.toString() + "_count", timers[type.ordinal()].getCount());
58+
}
59+
return Collections.unmodifiableMap(map);
60+
}
61+
62+
public final AggregatedQueryLeafProfilerResult getLeafProfilerResult(Thread thread) {
63+
final Map<String, Long> map = CollectionUtil.newHashMap(LEAF_LEVEL_TIMING_TYPE.size() * 2);
64+
long sliceStartTime = Long.MAX_VALUE;
65+
long sliceEndTime = Long.MIN_VALUE;
66+
for (QueryProfilerTimingType type : LEAF_LEVEL_TIMING_TYPE) {
67+
final QueryProfilerTimer timer = timers[type.ordinal()];
68+
// Consider timer for updating start/total time only
69+
// if it was used
70+
if (timer.getCount() > 0) {
71+
sliceStartTime = Math.min(sliceStartTime, timer.getEarliestTimerStartTime());
72+
sliceEndTime =
73+
Math.max(
74+
sliceEndTime, timer.getEarliestTimerStartTime() + timer.getApproximateTiming());
75+
}
76+
map.put(type.toString(), timer.getApproximateTiming());
77+
map.put(type.toString() + "_count", timer.getCount());
78+
}
79+
return new AggregatedQueryLeafProfilerResult(
80+
thread, map, sliceStartTime, sliceEndTime - sliceStartTime);
81+
}
82+
83+
public final long toTotalTime() {
84+
long total = 0;
85+
for (QueryProfilerTimer timer : timers) {
86+
total += timer.getApproximateTiming();
87+
}
88+
return total;
89+
}
90+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.lucene.sandbox.search;
19+
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.concurrent.ConcurrentHashMap;
23+
import java.util.concurrent.ConcurrentMap;
24+
25+
/** QueryLeafProfilerAggregator that aggregates leaf breakdowns at thread level */
26+
class QueryLeafProfilerThreadAggregator {
27+
private final ConcurrentMap<Thread, QueryLeafProfilerBreakdown> queryThreadBreakdowns;
28+
private long queryStartTime = Long.MAX_VALUE;
29+
private long queryTotalTime = 0;
30+
31+
public QueryLeafProfilerThreadAggregator() {
32+
queryThreadBreakdowns = new ConcurrentHashMap<>();
33+
}
34+
35+
public long getQueryStartTime() {
36+
return queryStartTime;
37+
}
38+
39+
public long getQueryTotalTime() {
40+
return queryTotalTime;
41+
}
42+
43+
private QueryLeafProfilerBreakdown getQueryThreadProfilerBreakdown() {
44+
final Thread currentThread = Thread.currentThread();
45+
// See please https://bugs.openjdk.java.net/browse/JDK-8161372
46+
final QueryLeafProfilerBreakdown profilerBreakdown = queryThreadBreakdowns.get(currentThread);
47+
48+
if (profilerBreakdown != null) {
49+
return profilerBreakdown;
50+
}
51+
52+
return queryThreadBreakdowns.computeIfAbsent(
53+
currentThread, _ -> new QueryLeafProfilerBreakdown());
54+
}
55+
56+
public QueryProfilerTimer getTimer(QueryProfilerTimingType timingType) {
57+
assert timingType.isLeafLevel();
58+
59+
return getQueryThreadProfilerBreakdown().getTimer(timingType);
60+
}
61+
62+
public List<AggregatedQueryLeafProfilerResult> getAggregatedQueryLeafProfilerResults() {
63+
final List<AggregatedQueryLeafProfilerResult> profilerResults = new ArrayList<>();
64+
for (Thread thread : queryThreadBreakdowns.keySet()) {
65+
final AggregatedQueryLeafProfilerResult aggregatedQueryLeafProfilerResult =
66+
queryThreadBreakdowns.get(thread).getLeafProfilerResult(thread);
67+
queryStartTime = Math.min(queryStartTime, aggregatedQueryLeafProfilerResult.getStartTime());
68+
queryTotalTime += aggregatedQueryLeafProfilerResult.getTotalTime();
69+
profilerResults.add(aggregatedQueryLeafProfilerResult);
70+
}
71+
72+
return profilerResults;
73+
}
74+
}

lucene/sandbox/src/java/org/apache/lucene/sandbox/search/QueryProfilerBreakdown.java

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,46 +17,89 @@
1717

1818
package org.apache.lucene.sandbox.search;
1919

20+
import java.util.Arrays;
21+
import java.util.Collection;
2022
import java.util.Collections;
23+
import java.util.HashMap;
24+
import java.util.List;
2125
import java.util.Map;
26+
import org.apache.lucene.search.Query;
2227
import org.apache.lucene.util.CollectionUtil;
2328

2429
/**
2530
* A record of timings for the various operations that may happen during query execution. A node's
2631
* time may be composed of several internal attributes (rewriting, weighting, scoring, etc).
2732
*/
2833
class QueryProfilerBreakdown {
34+
private static final Collection<QueryProfilerTimingType> QUERY_LEVEL_TIMING_TYPE =
35+
Arrays.stream(QueryProfilerTimingType.values()).filter(t -> !t.isLeafLevel()).toList();
36+
private final Map<QueryProfilerTimingType, QueryProfilerTimer> queryProfilerTimers;
2937

30-
/** The accumulated timings for this query node */
31-
private final QueryProfilerTimer[] timers;
38+
private final QueryLeafProfilerThreadAggregator queryLeafProfilerAggregator;
3239

3340
/** Sole constructor. */
3441
public QueryProfilerBreakdown() {
35-
timers = new QueryProfilerTimer[QueryProfilerTimingType.values().length];
36-
for (int i = 0; i < timers.length; ++i) {
37-
timers[i] = new QueryProfilerTimer();
42+
queryProfilerTimers = new HashMap<>();
43+
this.queryLeafProfilerAggregator = new QueryLeafProfilerThreadAggregator();
44+
45+
for (QueryProfilerTimingType timingType : QUERY_LEVEL_TIMING_TYPE) {
46+
queryProfilerTimers.put(timingType, new QueryProfilerTimer());
3847
}
3948
}
4049

41-
public QueryProfilerTimer getTimer(QueryProfilerTimingType type) {
42-
return timers[type.ordinal()];
50+
public final QueryProfilerTimer getTimer(QueryProfilerTimingType timingType) {
51+
if (timingType.isLeafLevel()) {
52+
return queryLeafProfilerAggregator.getTimer(timingType);
53+
}
54+
55+
// Return the query level profiler timer if not
56+
// slice level
57+
return queryProfilerTimers.get(timingType);
4358
}
4459

4560
/** Build a timing count breakdown. */
46-
public final Map<String, Long> toBreakdownMap() {
47-
Map<String, Long> map = CollectionUtil.newHashMap(timers.length * 2);
48-
for (QueryProfilerTimingType type : QueryProfilerTimingType.values()) {
49-
map.put(type.toString(), timers[type.ordinal()].getApproximateTiming());
50-
map.put(type.toString() + "_count", timers[type.ordinal()].getCount());
61+
public final QueryProfilerResult getQueryProfilerResult(
62+
Query query, List<QueryProfilerResult> childrenProfileResults) {
63+
long queryStartTime = Long.MAX_VALUE;
64+
long queryTotalTime = 0;
65+
final Map<String, Long> breakdownMap =
66+
CollectionUtil.newHashMap(QUERY_LEVEL_TIMING_TYPE.size() * 2);
67+
for (QueryProfilerTimingType type : QUERY_LEVEL_TIMING_TYPE) {
68+
final QueryProfilerTimer timer = queryProfilerTimers.get(type);
69+
if (timer.getCount() > 0) {
70+
queryStartTime = Math.min(queryStartTime, timer.getEarliestTimerStartTime());
71+
queryTotalTime += timer.getApproximateTiming();
72+
}
73+
// TODO: Should we put only non-zero timer values in the final output?
74+
breakdownMap.put(type.toString(), queryProfilerTimers.get(type).getApproximateTiming());
75+
breakdownMap.put(type.toString() + "_count", queryProfilerTimers.get(type).getCount());
5176
}
52-
return Collections.unmodifiableMap(map);
77+
78+
final List<AggregatedQueryLeafProfilerResult> threadProfilerResults =
79+
queryLeafProfilerAggregator.getAggregatedQueryLeafProfilerResults();
80+
queryStartTime = Math.min(queryStartTime, queryLeafProfilerAggregator.getQueryStartTime());
81+
queryTotalTime += queryLeafProfilerAggregator.getQueryTotalTime();
82+
83+
return new QueryProfilerResult(
84+
getTypeFromQuery(query),
85+
getDescriptionFromQuery(query),
86+
Collections.unmodifiableMap(breakdownMap),
87+
threadProfilerResults,
88+
childrenProfileResults,
89+
queryStartTime,
90+
queryTotalTime);
5391
}
5492

55-
public final long toTotalTime() {
56-
long total = 0;
57-
for (QueryProfilerTimer timer : timers) {
58-
total += timer.getApproximateTiming();
93+
private String getTypeFromQuery(Query query) {
94+
// Anonymous classes won't have a name,
95+
// we need to get the super class
96+
if (query.getClass().getSimpleName().isEmpty()) {
97+
return query.getClass().getSuperclass().getSimpleName();
5998
}
60-
return total;
99+
return query.getClass().getSimpleName();
100+
}
101+
102+
private String getDescriptionFromQuery(Query query) {
103+
return query.toString();
61104
}
62105
}

0 commit comments

Comments
 (0)