Skip to content

Commit cbc5507

Browse files
committed
Handle negative search request nodes stats
Signed-off-by: David Zane <[email protected]>
1 parent 8df81fd commit cbc5507

File tree

4 files changed

+66
-4
lines changed

4 files changed

+66
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
7676
- Fix ingestion state xcontent serialization in IndexMetadata and fail fast on mapping errors([#19320](https://github.com/opensearch-project/OpenSearch/pull/19320))
7777
- Fix updated keyword field params leading to stale responses from request cache ([#19385](https://github.com/opensearch-project/OpenSearch/pull/19385))
7878
- Implement SslHandler retrieval logic for transport-reactor-netty4 plugin ([#19458](https://github.com/opensearch-project/OpenSearch/pull/19458))
79+
- Handle negative search request nodes stats ([#19340](https://github.com/opensearch-project/OpenSearch/pull/19340))
7980

8081
### Dependencies
8182
- Bump `com.gradleup.shadow:shadow-gradle-plugin` from 8.3.5 to 8.3.9 ([#19400](https://github.com/opensearch-project/OpenSearch/pull/19400))

server/src/main/java/org/opensearch/action/search/SearchRequestStats.java

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

99
package org.opensearch.action.search;
1010

11+
import org.apache.logging.log4j.LogManager;
12+
import org.apache.logging.log4j.Logger;
1113
import org.opensearch.common.annotation.PublicApi;
1214
import org.opensearch.common.inject.Inject;
1315
import org.opensearch.common.metrics.CounterMetric;
@@ -28,6 +30,7 @@
2830
public final class SearchRequestStats extends SearchRequestOperationsListener {
2931
Map<SearchPhaseName, StatsHolder> phaseStatsMap = new EnumMap<>(SearchPhaseName.class);
3032
StatsHolder tookStatsHolder;
33+
private static final Logger logger = LogManager.getLogger(SearchRequestStats.class);
3134

3235
public static final String SEARCH_REQUEST_STATS_ENABLED_KEY = "search.request_stats_enabled";
3336
public static final Setting<Boolean> SEARCH_REQUEST_STATS_ENABLED = Setting.boolSetting(
@@ -80,6 +83,9 @@ protected void onPhaseStart(SearchPhaseContext context) {
8083
protected void onPhaseEnd(SearchPhaseContext context, SearchRequestContext searchRequestContext) {
8184
context.getCurrentPhase().getSearchPhaseNameOptional().ifPresent(name -> {
8285
StatsHolder phaseStats = phaseStatsMap.get(name);
86+
if (phaseStats.current.count() == 0) {
87+
logger.warn("SearchRequestStats 'current' went negative for phase '{}'", name);
88+
}
8389
phaseStats.current.dec();
8490
phaseStats.total.inc();
8591
phaseStats.timing.inc(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - context.getCurrentPhase().getStartTimeInNanos()));

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("'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("'timeInMillis' is negative: [{}]", timeInMillis);
100+
out.writeVLong(0);
101+
} else {
102+
out.writeVLong(timeInMillis);
103+
}
92104
}
93105

94106
PhaseStatsLongHolder() {
@@ -177,7 +189,7 @@ public RequestStatsLongHolder getRequestStatsLongHolder() {
177189
return requestStatsLongHolder;
178190
}
179191

180-
private Stats() {
192+
Stats() {
181193
// for internal use, initializes all counts to 0
182194
}
183195

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@
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;
4344
import org.opensearch.test.OpenSearchTestCase;
45+
import org.junit.Assert;
4446

4547
import java.util.HashMap;
4648
import java.util.Map;
@@ -159,4 +161,45 @@ private static void assertStats(Stats stats, long equalTo) {
159161
// avg_concurrency is not summed up across stats
160162
assertEquals(1, stats.getConcurrentAvgSliceCount(), 0);
161163
}
164+
165+
public void testNegativeRequestStats() throws Exception {
166+
SearchStats searchStats = new SearchStats(new Stats(), 0, new HashMap<>());
167+
168+
long paramValue = randomIntBetween(2, 50);
169+
170+
// Testing for request stats
171+
ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);
172+
SearchRequestStats testRequestStats = new SearchRequestStats(clusterSettings);
173+
SearchPhaseContext ctx = mock(SearchPhaseContext.class);
174+
for (SearchPhaseName searchPhaseName : SearchPhaseName.values()) {
175+
SearchPhase mockSearchPhase = mock(SearchPhase.class);
176+
when(ctx.getCurrentPhase()).thenReturn(mockSearchPhase);
177+
when(mockSearchPhase.getStartTimeInNanos()).thenReturn(System.nanoTime() - TimeUnit.SECONDS.toNanos(paramValue));
178+
when(mockSearchPhase.getSearchPhaseNameOptional()).thenReturn(Optional.ofNullable(searchPhaseName));
179+
for (int iterator = 0; iterator < paramValue; iterator++) {
180+
onPhaseStart(testRequestStats, ctx);
181+
onPhaseEnd(testRequestStats, ctx);
182+
onPhaseEnd(testRequestStats, ctx); // call onPhaseEnd() twice to make 'current' negative
183+
}
184+
}
185+
searchStats.setSearchRequestStats(testRequestStats);
186+
for (SearchPhaseName searchPhaseName : SearchPhaseName.values()) {
187+
Assert.assertNotNull(searchStats.getTotal().getRequestStatsLongHolder());
188+
assertEquals(
189+
-1 * paramValue, // current is negative, equals -1 * paramValue (num loop iterations)
190+
searchStats.getTotal().getRequestStatsLongHolder().getRequestStatsHolder().get(searchPhaseName.getName()).current
191+
);
192+
assertEquals(
193+
2 * paramValue,
194+
searchStats.getTotal().getRequestStatsLongHolder().getRequestStatsHolder().get(searchPhaseName.getName()).total
195+
);
196+
assertThat(
197+
searchStats.getTotal().getRequestStatsLongHolder().getRequestStatsHolder().get(searchPhaseName.getName()).timeInMillis,
198+
greaterThanOrEqualTo(paramValue)
199+
);
200+
}
201+
202+
// Ensure writeTo() does not throw error with negative 'current'
203+
searchStats.writeTo(new BytesStreamOutput(10));
204+
}
162205
}

0 commit comments

Comments
 (0)