Skip to content

Commit 2ab7bfd

Browse files
ticheng-awssohami
andcommitted
Add support for query profiler with concurrent aggregation (#9248)
* Add support for query profiler with concurrent aggregation (#9248) Signed-off-by: Ticheng Lin <ticheng@amazon.com> * Refactor and work on the PR comments Signed-off-by: Ticheng Lin <ticheng@amazon.com> * Update collectorToLeaves mapping for children breakdowns post profile metric collection and before creating the results Signed-off-by: Sorabh Hamirwasia <sohami.apache@gmail.com> * Refactor logic to compute the slice level breakdown stats and query level breakdown stats for concurrent search case Signed-off-by: Sorabh Hamirwasia <sohami.apache@gmail.com> * Fix QueryProfilePhaseTests and QueryProfileTests, and parameterize QueryProfilerIT with concurrent search enabled Signed-off-by: Ticheng Lin <ticheng@amazon.com> * Handle the case when there are no leaf context to compute the profile stats to return default stats for all breakdown type along with min/max/avg values. Replace queryStart and queryEnd time with queryNodeTime Signed-off-by: Sorabh Hamirwasia <sohami.apache@gmail.com> * Add UTs for ConcurrentQueryProfileBreakdown Signed-off-by: Sorabh Hamirwasia <sohami.apache@gmail.com> * Add concurrent search stats test into the QueryProfilerIT Signed-off-by: Ticheng Lin <ticheng@amazon.com> * Address review comments Signed-off-by: Sorabh Hamirwasia <sohami.apache@gmail.com> --------- Signed-off-by: Ticheng Lin <ticheng@amazon.com> Signed-off-by: Sorabh Hamirwasia <sohami.apache@gmail.com> Co-authored-by: Sorabh Hamirwasia <sohami.apache@gmail.com>
1 parent 7922295 commit 2ab7bfd

16 files changed

+1114
-108
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1919
- Add cluster state stats ([#10670](https://github.com/opensearch-project/OpenSearch/pull/10670))
2020
- [Remote cluster state] Restore cluster state version during remote state auto restore ([#10853](https://github.com/opensearch-project/OpenSearch/pull/10853))
2121
- Update the indexRandom function to create more segments for concurrent search tests ([10247](https://github.com/opensearch-project/OpenSearch/pull/10247))
22+
- Add support for query profiler with concurrent aggregation ([#9248](https://github.com/opensearch-project/OpenSearch/pull/9248))
2223

2324
### Dependencies
2425
- Bumps jetty version to 9.4.52.v20230823 to fix GMS-2023-1857 ([#9822](https://github.com/opensearch-project/OpenSearch/pull/9822))

server/src/internalClusterTest/java/org/opensearch/search/profile/query/QueryProfilerIT.java

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
package org.opensearch.search.profile.query;
3434

35+
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
36+
3537
import org.apache.lucene.tests.util.English;
3638
import org.opensearch.action.index.IndexRequestBuilder;
3739
import org.opensearch.action.search.MultiSearchResponse;
@@ -40,29 +42,56 @@
4042
import org.opensearch.action.search.SearchType;
4143
import org.opensearch.action.search.ShardSearchFailure;
4244
import org.opensearch.common.settings.Settings;
45+
import org.opensearch.common.util.FeatureFlags;
4346
import org.opensearch.index.query.QueryBuilder;
4447
import org.opensearch.index.query.QueryBuilders;
4548
import org.opensearch.search.SearchHit;
4649
import org.opensearch.search.profile.ProfileResult;
4750
import org.opensearch.search.profile.ProfileShardResult;
4851
import org.opensearch.search.sort.SortOrder;
49-
import org.opensearch.test.OpenSearchIntegTestCase;
52+
import org.opensearch.test.ParameterizedOpenSearchIntegTestCase;
5053

5154
import java.util.Arrays;
55+
import java.util.Collection;
5256
import java.util.HashSet;
5357
import java.util.List;
5458
import java.util.Map;
5559
import java.util.Set;
5660

61+
import static org.opensearch.search.SearchService.CLUSTER_CONCURRENT_SEGMENT_SEARCH_SETTING;
5762
import static org.opensearch.search.profile.query.RandomQueryGenerator.randomQueryBuilder;
5863
import static org.hamcrest.Matchers.emptyOrNullString;
5964
import static org.hamcrest.Matchers.equalTo;
6065
import static org.hamcrest.Matchers.greaterThan;
6166
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
6267
import static org.hamcrest.Matchers.is;
6368
import static org.hamcrest.Matchers.not;
69+
import static org.hamcrest.Matchers.nullValue;
70+
71+
public class QueryProfilerIT extends ParameterizedOpenSearchIntegTestCase {
72+
private final boolean concurrentSearchEnabled;
73+
private static final String MAX_PREFIX = "max_";
74+
private static final String MIN_PREFIX = "min_";
75+
private static final String AVG_PREFIX = "avg_";
76+
private static final String TIMING_TYPE_COUNT_SUFFIX = "_count";
77+
78+
public QueryProfilerIT(Settings settings, boolean concurrentSearchEnabled) {
79+
super(settings);
80+
this.concurrentSearchEnabled = concurrentSearchEnabled;
81+
}
82+
83+
@ParametersFactory
84+
public static Collection<Object[]> parameters() {
85+
return Arrays.asList(
86+
new Object[] { Settings.builder().put(CLUSTER_CONCURRENT_SEGMENT_SEARCH_SETTING.getKey(), false).build(), false },
87+
new Object[] { Settings.builder().put(CLUSTER_CONCURRENT_SEGMENT_SEARCH_SETTING.getKey(), true).build(), true }
88+
);
89+
}
6490

65-
public class QueryProfilerIT extends OpenSearchIntegTestCase {
91+
@Override
92+
protected Settings featureFlagSettings() {
93+
return Settings.builder().put(super.featureFlagSettings()).put(FeatureFlags.CONCURRENT_SEGMENT_SEARCH, "true").build();
94+
}
6695

6796
/**
6897
* This test simply checks to make sure nothing crashes. Test indexes 100-150 documents,
@@ -229,6 +258,7 @@ public void testSimpleMatch() throws Exception {
229258
assertEquals(result.getLuceneDescription(), "field1:one");
230259
assertThat(result.getTime(), greaterThan(0L));
231260
assertNotNull(result.getTimeBreakdown());
261+
assertQueryProfileResult(result);
232262
}
233263

234264
CollectorResult result = searchProfiles.getCollectorResult();
@@ -271,6 +301,7 @@ public void testBool() throws Exception {
271301
assertThat(result.getTime(), greaterThan(0L));
272302
assertNotNull(result.getTimeBreakdown());
273303
assertEquals(result.getProfiledChildren().size(), 2);
304+
assertQueryProfileResult(result);
274305

275306
// Check the children
276307
List<ProfileResult> children = result.getProfiledChildren();
@@ -282,12 +313,14 @@ public void testBool() throws Exception {
282313
assertThat(childProfile.getTime(), greaterThan(0L));
283314
assertNotNull(childProfile.getTimeBreakdown());
284315
assertEquals(childProfile.getProfiledChildren().size(), 0);
316+
assertQueryProfileResult(childProfile);
285317

286318
childProfile = children.get(1);
287319
assertEquals(childProfile.getQueryName(), "TermQuery");
288320
assertEquals(childProfile.getLuceneDescription(), "field1:two");
289321
assertThat(childProfile.getTime(), greaterThan(0L));
290322
assertNotNull(childProfile.getTimeBreakdown());
323+
assertQueryProfileResult(childProfile);
291324
}
292325

293326
CollectorResult result = searchProfiles.getCollectorResult();
@@ -330,6 +363,7 @@ public void testEmptyBool() throws Exception {
330363
assertNotNull(result.getLuceneDescription());
331364
assertThat(result.getTime(), greaterThan(0L));
332365
assertNotNull(result.getTimeBreakdown());
366+
assertQueryProfileResult(result);
333367
}
334368

335369
CollectorResult result = searchProfiles.getCollectorResult();
@@ -375,6 +409,7 @@ public void testCollapsingBool() throws Exception {
375409
assertNotNull(result.getLuceneDescription());
376410
assertThat(result.getTime(), greaterThan(0L));
377411
assertNotNull(result.getTimeBreakdown());
412+
assertQueryProfileResult(result);
378413
}
379414

380415
CollectorResult result = searchProfiles.getCollectorResult();
@@ -415,6 +450,7 @@ public void testBoosting() throws Exception {
415450
assertNotNull(result.getLuceneDescription());
416451
assertThat(result.getTime(), greaterThan(0L));
417452
assertNotNull(result.getTimeBreakdown());
453+
assertQueryProfileResult(result);
418454
}
419455

420456
CollectorResult result = searchProfiles.getCollectorResult();
@@ -455,6 +491,7 @@ public void testDisMaxRange() throws Exception {
455491
assertNotNull(result.getLuceneDescription());
456492
assertThat(result.getTime(), greaterThan(0L));
457493
assertNotNull(result.getTimeBreakdown());
494+
assertQueryProfileResult(result);
458495
}
459496

460497
CollectorResult result = searchProfiles.getCollectorResult();
@@ -494,6 +531,7 @@ public void testRange() throws Exception {
494531
assertNotNull(result.getLuceneDescription());
495532
assertThat(result.getTime(), greaterThan(0L));
496533
assertNotNull(result.getTimeBreakdown());
534+
assertQueryProfileResult(result);
497535
}
498536

499537
CollectorResult result = searchProfiles.getCollectorResult();
@@ -547,6 +585,7 @@ public void testPhrase() throws Exception {
547585
assertNotNull(result.getLuceneDescription());
548586
assertThat(result.getTime(), greaterThan(0L));
549587
assertNotNull(result.getTimeBreakdown());
588+
assertQueryProfileResult(result);
550589
}
551590

552591
CollectorResult result = searchProfiles.getCollectorResult();
@@ -579,4 +618,35 @@ public void testNoProfile() throws Exception {
579618
assertThat("Profile response element should be an empty map", resp.getProfileResults().size(), equalTo(0));
580619
}
581620

621+
private void assertQueryProfileResult(ProfileResult result) {
622+
Map<String, Long> breakdown = result.getTimeBreakdown();
623+
Long maxSliceTime = result.getMaxSliceTime();
624+
Long minSliceTime = result.getMinSliceTime();
625+
Long avgSliceTime = result.getAvgSliceTime();
626+
if (concurrentSearchEnabled) {
627+
assertNotNull(maxSliceTime);
628+
assertNotNull(minSliceTime);
629+
assertNotNull(avgSliceTime);
630+
assertThat(breakdown.size(), equalTo(66));
631+
for (QueryTimingType queryTimingType : QueryTimingType.values()) {
632+
if (queryTimingType != QueryTimingType.CREATE_WEIGHT) {
633+
String maxTimingType = MAX_PREFIX + queryTimingType;
634+
String minTimingType = MIN_PREFIX + queryTimingType;
635+
String avgTimingType = AVG_PREFIX + queryTimingType;
636+
assertNotNull(breakdown.get(maxTimingType));
637+
assertNotNull(breakdown.get(minTimingType));
638+
assertNotNull(breakdown.get(avgTimingType));
639+
assertNotNull(breakdown.get(maxTimingType + TIMING_TYPE_COUNT_SUFFIX));
640+
assertNotNull(breakdown.get(minTimingType + TIMING_TYPE_COUNT_SUFFIX));
641+
assertNotNull(breakdown.get(avgTimingType + TIMING_TYPE_COUNT_SUFFIX));
642+
}
643+
}
644+
} else {
645+
assertThat(maxSliceTime, is(nullValue()));
646+
assertThat(minSliceTime, is(nullValue()));
647+
assertThat(avgSliceTime, is(nullValue()));
648+
assertThat(breakdown.size(), equalTo(27));
649+
}
650+
}
651+
582652
}

server/src/main/java/org/opensearch/search/internal/ContextIndexSearcher.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,9 @@ private void searchLeaf(LeafReaderContext ctx, Weight weight, Collector collecto
299299
final LeafCollector leafCollector;
300300
try {
301301
cancellable.checkCancelled();
302+
if (weight instanceof ProfileWeight) {
303+
((ProfileWeight) weight).associateCollectorToLeaves(ctx, collector);
304+
}
302305
weight = wrapWeight(weight);
303306
// See please https://github.com/apache/lucene/pull/964
304307
collector.setWeight(weight);

server/src/main/java/org/opensearch/search/profile/AbstractInternalProfileTree.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ private ProfileResult doGetTree(int token) {
180180
// calculating the same times over and over...but worth the effort?
181181
String type = getTypeFromElement(element);
182182
String description = getDescriptionFromElement(element);
183+
return createProfileResult(type, description, breakdown, childrenProfileResults);
184+
}
185+
186+
protected ProfileResult createProfileResult(String type, String description, PB breakdown, List<ProfileResult> childrenProfileResults) {
183187
return new ProfileResult(
184188
type,
185189
description,

server/src/main/java/org/opensearch/search/profile/AbstractProfileBreakdown.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,19 @@ public Map<String, Long> toBreakdownMap() {
8080
for (T timingType : this.timingTypes) {
8181
map.put(timingType.toString(), this.timings[timingType.ordinal()].getApproximateTiming());
8282
map.put(timingType + TIMING_TYPE_COUNT_SUFFIX, this.timings[timingType.ordinal()].getCount());
83+
map.put(timingType + TIMING_TYPE_START_TIME_SUFFIX, this.timings[timingType.ordinal()].getEarliestTimerStartTime());
8384
}
8485
return Collections.unmodifiableMap(map);
8586
}
8687

8788
/**
8889
* Fetch extra debugging information.
8990
*/
90-
protected Map<String, Object> toDebugMap() {
91+
public Map<String, Object> toDebugMap() {
9192
return emptyMap();
9293
}
9394

94-
public final long toNodeTime() {
95+
public long toNodeTime() {
9596
long total = 0;
9697
for (T timingType : timingTypes) {
9798
total += timings[timingType.ordinal()].getApproximateTiming();

server/src/main/java/org/opensearch/search/profile/ContextualProfileBreakdown.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
package org.opensearch.search.profile;
1010

11+
import org.apache.lucene.index.LeafReaderContext;
12+
import org.apache.lucene.search.Collector;
13+
14+
import java.util.List;
15+
import java.util.Map;
16+
1117
/**
1218
* Provide contextual profile breakdowns which are associated with freestyle context. Used when concurrent
1319
* search over segments is activated and each collector needs own non-shareable profile breakdown instance.
@@ -25,4 +31,8 @@ public ContextualProfileBreakdown(Class<T> clazz) {
2531
* @return contextual profile breakdown instance
2632
*/
2733
public abstract AbstractProfileBreakdown<T> context(Object context);
34+
35+
public void associateCollectorToLeaves(Collector collector, LeafReaderContext leaf) {}
36+
37+
public void associateCollectorsToLeaves(Map<Collector, List<LeafReaderContext>> collectorToLeaves) {}
2838
}

server/src/main/java/org/opensearch/search/profile/aggregation/AggregationProfileBreakdown.java

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434

3535
import org.opensearch.search.profile.AbstractProfileBreakdown;
3636

37-
import java.util.Collections;
3837
import java.util.HashMap;
3938
import java.util.Map;
4039

@@ -60,21 +59,7 @@ public void addDebugInfo(String key, Object value) {
6059
}
6160

6261
@Override
63-
protected Map<String, Object> toDebugMap() {
62+
public Map<String, Object> toDebugMap() {
6463
return unmodifiableMap(extra);
6564
}
66-
67-
/**
68-
* Build a timing count startTime breakdown for aggregation timing types
69-
*/
70-
@Override
71-
public Map<String, Long> toBreakdownMap() {
72-
Map<String, Long> map = new HashMap<>(timings.length * 3);
73-
for (AggregationTimingType timingType : timingTypes) {
74-
map.put(timingType.toString(), timings[timingType.ordinal()].getApproximateTiming());
75-
map.put(timingType + TIMING_TYPE_COUNT_SUFFIX, timings[timingType.ordinal()].getCount());
76-
map.put(timingType + TIMING_TYPE_START_TIME_SUFFIX, timings[timingType.ordinal()].getEarliestTimerStartTime());
77-
}
78-
return Collections.unmodifiableMap(map);
79-
}
8065
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.search.profile.query;
10+
11+
import org.apache.lucene.search.Query;
12+
import org.opensearch.search.profile.AbstractInternalProfileTree;
13+
import org.opensearch.search.profile.ContextualProfileBreakdown;
14+
import org.opensearch.search.profile.ProfileResult;
15+
16+
/**
17+
* This class tracks the dependency tree for queries (scoring and rewriting) and
18+
* generates {@link QueryProfileBreakdown} for each node in the tree. It also finalizes the tree
19+
* and returns a list of {@link ProfileResult} that can be serialized back to the client
20+
*
21+
* @opensearch.internal
22+
*/
23+
public abstract class AbstractQueryProfileTree extends AbstractInternalProfileTree<ContextualProfileBreakdown<QueryTimingType>, Query> {
24+
25+
/** Rewrite time */
26+
private long rewriteTime;
27+
private long rewriteScratch;
28+
29+
@Override
30+
protected String getTypeFromElement(Query query) {
31+
// Anonymous classes won't have a name,
32+
// we need to get the super class
33+
if (query.getClass().getSimpleName().isEmpty()) {
34+
return query.getClass().getSuperclass().getSimpleName();
35+
}
36+
return query.getClass().getSimpleName();
37+
}
38+
39+
@Override
40+
protected String getDescriptionFromElement(Query query) {
41+
return query.toString();
42+
}
43+
44+
/**
45+
* Begin timing a query for a specific Timing context
46+
*/
47+
public void startRewriteTime() {
48+
assert rewriteScratch == 0;
49+
rewriteScratch = System.nanoTime();
50+
}
51+
52+
/**
53+
* Halt the timing process and add the elapsed rewriting time.
54+
* startRewriteTime() must be called for a particular context prior to calling
55+
* stopAndAddRewriteTime(), otherwise the elapsed time will be negative and
56+
* nonsensical
57+
*
58+
* @return The elapsed time
59+
*/
60+
public long stopAndAddRewriteTime() {
61+
long time = Math.max(1, System.nanoTime() - rewriteScratch);
62+
rewriteTime += time;
63+
rewriteScratch = 0;
64+
return time;
65+
}
66+
67+
public long getRewriteTime() {
68+
return rewriteTime;
69+
}
70+
}

0 commit comments

Comments
 (0)