Skip to content

Commit 40f66a4

Browse files
LUCENE-9965: Add tooling to introspect query execution time (#144)
This change adds new IndexSearcher and Collector implementations to profile search execution and break down the timings. The breakdown includes the total time spent in each of the following categories along with the number of times visited: create weight, build scorer, next doc, advance, score, match. Co-authored-by: Julie Tibshirani <[email protected]>
1 parent f5e050b commit 40f66a4

15 files changed

+1579
-0
lines changed

lucene/CHANGES.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,33 @@ Other
354354

355355
* LUCENE-9976: Fix WANDScorer assertion error. (Zach Chen, Adrien Grand, Dawid Weiss)
356356

357+
======================= Lucene 8.10.0 =======================
358+
359+
API Changes
360+
---------------------
361+
(No changes)
362+
363+
New Features
364+
---------------------
365+
(No changes)
366+
367+
Improvements
368+
---------------------
369+
* LUCENE-9965: Added QueryProfilerIndexSearcher and ProfilerCollector to support debugging
370+
query execution strategy and timing. (Jack Conradson, Julie Tibshirani)
371+
372+
Optimizations
373+
---------------------
374+
(No changes)
375+
376+
Bug Fixes
377+
---------------------
378+
(No changes)
379+
380+
Other
381+
---------------------
382+
(No changes)
383+
357384
======================= Lucene 8.9.0 =======================
358385

359386
API Changes
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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.io.IOException;
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
import org.apache.lucene.index.LeafReaderContext;
24+
import org.apache.lucene.search.Collector;
25+
import org.apache.lucene.search.LeafCollector;
26+
import org.apache.lucene.search.ScoreMode;
27+
28+
/**
29+
* This class wraps a Collector and times the execution of: - setScorer() - collect() -
30+
* doSetNextReader() - needsScores()
31+
*
32+
* <p>QueryProfiler facilitates the linking of the Collector graph
33+
*/
34+
public class ProfilerCollector implements Collector {
35+
36+
/** A more friendly representation of the Collector's class name */
37+
private final String collectorName;
38+
39+
/** A "hint" to help provide some context about this Collector */
40+
private final String reason;
41+
42+
/** The wrapped collector */
43+
private final ProfilerCollectorWrapper collector;
44+
45+
/** A list of "embedded" children collectors */
46+
private final List<ProfilerCollector> children;
47+
48+
public ProfilerCollector(Collector collector, String reason, List<ProfilerCollector> children) {
49+
this.collector = new ProfilerCollectorWrapper(collector);
50+
this.reason = reason;
51+
this.collectorName = deriveCollectorName(collector);
52+
this.children = children;
53+
}
54+
55+
/** @return the profiled time for this collector (inclusive of children) */
56+
public long getTime() {
57+
return collector.getTime();
58+
}
59+
60+
/** @return a human readable "hint" about what this collector was used for */
61+
public String getReason() {
62+
return this.reason;
63+
}
64+
65+
/** @return the lucene class name of the collector */
66+
public String getName() {
67+
return this.collectorName;
68+
}
69+
70+
/**
71+
* Creates a human-friendly representation of the Collector name.
72+
*
73+
* @param c The Collector to derive a name from
74+
* @return A (hopefully) prettier name
75+
*/
76+
private String deriveCollectorName(Collector c) {
77+
return c.getClass().getSimpleName();
78+
}
79+
80+
@Override
81+
public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
82+
return collector.getLeafCollector(context);
83+
}
84+
85+
@Override
86+
public ScoreMode scoreMode() {
87+
return collector.scoreMode();
88+
}
89+
90+
public ProfilerCollectorResult getProfileResult() {
91+
return ProfilerCollector.doGetCollectorTree(this);
92+
}
93+
94+
private static ProfilerCollectorResult doGetCollectorTree(ProfilerCollector collector) {
95+
List<ProfilerCollectorResult> childResults = new ArrayList<>(collector.children.size());
96+
for (ProfilerCollector child : collector.children) {
97+
ProfilerCollectorResult result = doGetCollectorTree(child);
98+
childResults.add(result);
99+
}
100+
return new ProfilerCollectorResult(
101+
collector.getName(), collector.getReason(), collector.getTime(), childResults);
102+
}
103+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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.List;
21+
22+
/**
23+
* Public class for profiled timings of the Collectors used in the search. Children
24+
* CollectorResult's may be embedded inside of a parent CollectorResult
25+
*/
26+
public class ProfilerCollectorResult {
27+
28+
/** A more friendly representation of the Collector's class name */
29+
private final String collectorName;
30+
31+
/** A "hint" to help provide some context about this Collector */
32+
private final String reason;
33+
34+
/** The total elapsed time for this Collector */
35+
private final long time;
36+
37+
/** A list of children collectors "embedded" inside this collector */
38+
private final List<ProfilerCollectorResult> children;
39+
40+
public ProfilerCollectorResult(
41+
String collectorName, String reason, Long time, List<ProfilerCollectorResult> children) {
42+
this.collectorName = collectorName;
43+
this.reason = reason;
44+
this.time = time;
45+
this.children = children;
46+
}
47+
48+
/** @return the profiled time for this collector (inclusive of children) */
49+
public long getTime() {
50+
return this.time;
51+
}
52+
53+
/** @return a human readable "hint" about what this collector was used for */
54+
public String getReason() {
55+
return this.reason;
56+
}
57+
58+
/** @return the lucene class name of the collector */
59+
public String getName() {
60+
return this.collectorName;
61+
}
62+
63+
/** @return a list of children collectors */
64+
public List<ProfilerCollectorResult> getProfiledChildren() {
65+
return children;
66+
}
67+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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.io.IOException;
21+
import org.apache.lucene.index.LeafReaderContext;
22+
import org.apache.lucene.search.Collector;
23+
import org.apache.lucene.search.FilterCollector;
24+
import org.apache.lucene.search.FilterLeafCollector;
25+
import org.apache.lucene.search.LeafCollector;
26+
import org.apache.lucene.search.Scorable;
27+
import org.apache.lucene.search.ScoreMode;
28+
29+
/** A collector that profiles how much time is spent calling it. */
30+
class ProfilerCollectorWrapper extends FilterCollector {
31+
32+
private long time;
33+
34+
/** Sole constructor. */
35+
ProfilerCollectorWrapper(Collector in) {
36+
super(in);
37+
}
38+
39+
@Override
40+
public ScoreMode scoreMode() {
41+
final long start = System.nanoTime();
42+
try {
43+
return super.scoreMode();
44+
} finally {
45+
time += Math.max(1, System.nanoTime() - start);
46+
}
47+
}
48+
49+
@Override
50+
public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
51+
final long start = System.nanoTime();
52+
final LeafCollector inLeafCollector;
53+
try {
54+
inLeafCollector = super.getLeafCollector(context);
55+
} finally {
56+
time += Math.max(1, System.nanoTime() - start);
57+
}
58+
return new FilterLeafCollector(inLeafCollector) {
59+
60+
@Override
61+
public void collect(int doc) throws IOException {
62+
final long start = System.nanoTime();
63+
try {
64+
super.collect(doc);
65+
} finally {
66+
time += Math.max(1, System.nanoTime() - start);
67+
}
68+
}
69+
70+
@Override
71+
public void setScorer(Scorable scorer) throws IOException {
72+
final long start = System.nanoTime();
73+
try {
74+
super.setScorer(scorer);
75+
} finally {
76+
time += Math.max(1, System.nanoTime() - start);
77+
}
78+
}
79+
};
80+
}
81+
82+
/** Return the total time spent on this collector. */
83+
public long getTime() {
84+
return time;
85+
}
86+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.HashMap;
22+
import java.util.Map;
23+
24+
/**
25+
* A record of timings for the various operations that may happen during query execution. A node's
26+
* time may be composed of several internal attributes (rewriting, weighting, scoring, etc).
27+
*/
28+
class QueryProfilerBreakdown {
29+
30+
/** The accumulated timings for this query node */
31+
private final QueryProfilerTimer[] timers;
32+
33+
/** Sole constructor. */
34+
public QueryProfilerBreakdown() {
35+
timers = new QueryProfilerTimer[QueryProfilerTimingType.values().length];
36+
for (int i = 0; i < timers.length; ++i) {
37+
timers[i] = new QueryProfilerTimer();
38+
}
39+
}
40+
41+
public QueryProfilerTimer getTimer(QueryProfilerTimingType type) {
42+
return timers[type.ordinal()];
43+
}
44+
45+
/** Build a timing count breakdown. */
46+
public final Map<String, Long> toBreakdownMap() {
47+
Map<String, Long> map = new HashMap<>(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());
51+
}
52+
return Collections.unmodifiableMap(map);
53+
}
54+
55+
public final long toTotalTime() {
56+
long total = 0;
57+
for (QueryProfilerTimer timer : timers) {
58+
total += timer.getApproximateTiming();
59+
}
60+
return total;
61+
}
62+
}

0 commit comments

Comments
 (0)