Skip to content

Commit e48ffba

Browse files
committed
Add check and handle negative SearchRequestStats
Signed-off-by: David Zane <[email protected]>
1 parent 5909e1a commit e48ffba

File tree

3 files changed

+58
-4
lines changed

3 files changed

+58
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1212
- Add Setting to adjust the primary constraint weights ([#16471](https://github.com/opensearch-project/OpenSearch/pull/16471))
1313
- Switch from `buildSrc/version.properties` to Gradle version catalog (`gradle/libs.versions.toml`) to enable dependabot to perform automated upgrades on common libs ([#16284](https://github.com/opensearch-project/OpenSearch/pull/16284))
1414
- Add dynamic setting allowing size > 0 requests to be cached in the request cache ([#16483](https://github.com/opensearch-project/OpenSearch/pull/16483))
15+
- Add check and handle negative SearchRequestStats ([#16569](https://github.com/opensearch-project/OpenSearch/pull/16569))
1516
- Make IndexStoreListener a pluggable interface ([#16583](https://github.com/opensearch-project/OpenSearch/pull/16583))
1617

1718
### Dependencies

server/src/main/java/org/opensearch/index/search/stats/SearchStats.java

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

3333
package org.opensearch.index.search.stats;
3434

35+
import org.apache.logging.log4j.LogManager;
36+
import org.apache.logging.log4j.Logger;
3537
import org.opensearch.Version;
3638
import org.opensearch.action.search.SearchPhaseName;
3739
import org.opensearch.action.search.SearchRequestStats;
@@ -67,7 +69,7 @@ public class SearchStats implements Writeable, ToXContentFragment {
6769
*/
6870
@PublicApi(since = "1.0.0")
6971
public static class PhaseStatsLongHolder implements Writeable {
70-
72+
private static final Logger logger = LogManager.getLogger(PhaseStatsLongHolder.class);
7173
long current;
7274
long total;
7375
long timeInMillis;
@@ -86,9 +88,19 @@ public long getTimeInMillis() {
8688

8789
@Override
8890
public void writeTo(StreamOutput out) throws IOException {
89-
out.writeVLong(current);
91+
if (current < 0) {
92+
logger.warn("Coordinator request stats 'current' is negative: {}", current);
93+
out.writeVLong(0);
94+
} else {
95+
out.writeVLong(current);
96+
}
9097
out.writeVLong(total);
91-
out.writeVLong(timeInMillis);
98+
if (timeInMillis < 0) {
99+
logger.warn("Coordinator request stats 'timeInMillis' is negative: {}", timeInMillis);
100+
out.writeVLong(0);
101+
} else {
102+
out.writeVLong(timeInMillis);
103+
}
92104
}
93105

94106
PhaseStatsLongHolder() {
@@ -173,7 +185,7 @@ public RequestStatsLongHolder getRequestStatsLongHolder() {
173185
return requestStatsLongHolder;
174186
}
175187

176-
private Stats() {
188+
Stats() {
177189
// for internal use, initializes all counts to 0
178190
}
179191

server/src/test/java/org/opensearch/index/search/stats/SearchStatsTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.opensearch.action.search.SearchPhaseName;
3838
import org.opensearch.action.search.SearchRequestOperationsListenerSupport;
3939
import org.opensearch.action.search.SearchRequestStats;
40+
import org.opensearch.common.io.stream.BytesStreamOutput;
4041
import org.opensearch.common.settings.ClusterSettings;
4142
import org.opensearch.common.settings.Settings;
4243
import org.opensearch.index.search.stats.SearchStats.Stats;
@@ -52,6 +53,46 @@
5253

5354
public class SearchStatsTests extends OpenSearchTestCase implements SearchRequestOperationsListenerSupport {
5455

56+
public void testNegativeRequestStats() throws Exception {
57+
SearchStats searchStats = new SearchStats(new Stats(), 0, new HashMap<>());
58+
59+
long paramValue = randomIntBetween(2, 50);
60+
61+
// Testing for request stats
62+
ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);
63+
SearchRequestStats testRequestStats = new SearchRequestStats(clusterSettings);
64+
SearchPhaseContext ctx = mock(SearchPhaseContext.class);
65+
for (SearchPhaseName searchPhaseName : SearchPhaseName.values()) {
66+
SearchPhase mockSearchPhase = mock(SearchPhase.class);
67+
when(ctx.getCurrentPhase()).thenReturn(mockSearchPhase);
68+
when(mockSearchPhase.getStartTimeInNanos()).thenReturn(System.nanoTime() - TimeUnit.SECONDS.toNanos(paramValue));
69+
when(mockSearchPhase.getSearchPhaseName()).thenReturn(searchPhaseName);
70+
for (int iterator = 0; iterator < paramValue; iterator++) {
71+
onPhaseStart(testRequestStats, ctx);
72+
onPhaseEnd(testRequestStats, ctx);
73+
onPhaseEnd(testRequestStats, ctx); // call onPhaseEnd() twice to make 'current' negative
74+
}
75+
}
76+
searchStats.setSearchRequestStats(testRequestStats);
77+
for (SearchPhaseName searchPhaseName : SearchPhaseName.values()) {
78+
assertEquals(
79+
-1 * paramValue, // current is negative, equals -1 * paramValue (num loop iterations)
80+
searchStats.getTotal().getRequestStatsLongHolder().getRequestStatsHolder().get(searchPhaseName.getName()).current
81+
);
82+
assertEquals(
83+
2 * paramValue,
84+
searchStats.getTotal().getRequestStatsLongHolder().getRequestStatsHolder().get(searchPhaseName.getName()).total
85+
);
86+
assertThat(
87+
searchStats.getTotal().getRequestStatsLongHolder().getRequestStatsHolder().get(searchPhaseName.getName()).timeInMillis,
88+
greaterThanOrEqualTo(paramValue)
89+
);
90+
}
91+
92+
// Ensure writeTo() does not throw error with negative 'current'
93+
searchStats.writeTo(new BytesStreamOutput(10));
94+
}
95+
5596
// https://github.com/elastic/elasticsearch/issues/7644
5697
public void testShardLevelSearchGroupStats() throws Exception {
5798
// let's create two dummy search stats with groups

0 commit comments

Comments
 (0)