Skip to content

Commit c2942ad

Browse files
committed
Add field type cache stats
Signed-off-by: David Zane <davizane@amazon.com>
1 parent 1d991b8 commit c2942ad

File tree

9 files changed

+187
-15
lines changed

9 files changed

+187
-15
lines changed

src/main/java/org/opensearch/plugin/insights/core/listener/QueryInsightsListener.java

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public QueryInsightsListener(
8787
this.clusterService = clusterService;
8888
this.queryInsightsService = queryInsightsService;
8989
this.queryShapeGenerator = new QueryShapeGenerator(clusterService);
90+
queryInsightsService.setQueryShapeGenerator(queryShapeGenerator);
9091

9192
// Setting endpoints set up for top n queries, including enabling top n queries, window size, and top n size
9293
// Expected metricTypes are Latency, CPU, and Memory.

src/main/java/org/opensearch/plugin/insights/core/service/QueryInsightsService.java

+22-3
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515

1616
import java.io.IOException;
1717
import java.util.ArrayList;
18+
import java.util.Collections;
1819
import java.util.Comparator;
1920
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
23+
import java.util.Optional;
2224
import java.util.concurrent.LinkedBlockingQueue;
2325
import java.util.concurrent.TimeUnit;
2426
import java.util.stream.Collectors;
@@ -36,6 +38,7 @@
3638
import org.opensearch.plugin.insights.core.metrics.OperationalMetric;
3739
import org.opensearch.plugin.insights.core.metrics.OperationalMetricsCounter;
3840
import org.opensearch.plugin.insights.core.reader.QueryInsightsReaderFactory;
41+
import org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator;
3942
import org.opensearch.plugin.insights.core.service.categorizer.SearchQueryCategorizer;
4043
import org.opensearch.plugin.insights.rules.model.GroupingType;
4144
import org.opensearch.plugin.insights.rules.model.MetricType;
@@ -100,9 +103,14 @@ public class QueryInsightsService extends AbstractLifecycleComponent {
100103

101104
private volatile boolean searchQueryMetricsEnabled;
102105

103-
private SearchQueryCategorizer searchQueryCategorizer;
106+
private final SearchQueryCategorizer searchQueryCategorizer;
104107

105-
private NamedXContentRegistry namedXContentRegistry;
108+
private final NamedXContentRegistry namedXContentRegistry;
109+
110+
/**
111+
* Query shape generator instance
112+
*/
113+
private QueryShapeGenerator queryShapeGenerator;
106114

107115
/**
108116
* Constructor of the QueryInsightsService
@@ -496,10 +504,14 @@ public QueryInsightsHealthStats getHealthStats() {
496504
Map<MetricType, TopQueriesHealthStats> topQueriesHealthStatsMap = topQueriesServices.entrySet()
497505
.stream()
498506
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getHealthStats()));
507+
Map<String, Long> fieldTypeCacheStats = Optional.ofNullable(queryShapeGenerator)
508+
.map(QueryShapeGenerator::getFieldTypeCacheStats)
509+
.orElse(Collections.emptyMap());
499510
return new QueryInsightsHealthStats(
500511
threadPool.info(QUERY_INSIGHTS_EXECUTOR),
501512
this.queryRecordsQueue.size(),
502-
topQueriesHealthStatsMap
513+
topQueriesHealthStatsMap,
514+
fieldTypeCacheStats
503515
);
504516
}
505517

@@ -511,4 +523,11 @@ private void deleteExpiredTopNIndices() {
511523
topQueriesServices.get(metricType).deleteExpiredTopNIndices(clusterService.state().metadata().indices());
512524
}
513525
}
526+
527+
/**
528+
* Set query shape generator
529+
*/
530+
public void setQueryShapeGenerator(final QueryShapeGenerator queryShapeGenerator) {
531+
this.queryShapeGenerator = queryShapeGenerator;
532+
}
514533
}

src/main/java/org/opensearch/plugin/insights/core/service/categorizer/IndicesFieldTypeCache.java

+46-4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ public class IndicesFieldTypeCache {
2828

2929
private static final Logger logger = LogManager.getLogger(IndicesFieldTypeCache.class);
3030
private final Cache<Index, IndexFieldMap> cache;
31+
/**
32+
* Count of items in the cache
33+
*/
34+
private final CounterMetric count;
35+
/**
36+
* Weight of cache in bytes
37+
*/
38+
private final CounterMetric weight;
3139

3240
public IndicesFieldTypeCache(Settings settings) {
3341
final long sizeInBytes = QueryCategorizationSettings.SEARCH_QUERY_FIELD_TYPE_CACHE_SIZE_KEY.get(settings).getBytes();
@@ -36,9 +44,11 @@ public IndicesFieldTypeCache(Settings settings) {
3644
cacheBuilder.setMaximumWeight(sizeInBytes).weigher((k, v) -> RamUsageEstimator.sizeOfObject(k) + v.weight());
3745
}
3846
cache = cacheBuilder.build();
47+
count = new CounterMetric();
48+
weight = new CounterMetric();
3949
}
4050

41-
public IndexFieldMap getOrInitialize(Index index) {
51+
IndexFieldMap getOrInitialize(Index index) {
4252
try {
4353
return cache.computeIfAbsent(index, k -> new IndexFieldMap());
4454
} catch (ExecutionException ex) {
@@ -52,16 +62,41 @@ public IndexFieldMap getOrInitialize(Index index) {
5262
}
5363

5464
public void invalidate(Index index) {
65+
count.dec(cache.get(index).fieldTypeMap.size());
66+
weight.dec(cache.get(index).weight());
5567
cache.invalidate(index);
5668
}
5769

5870
public Iterable<Index> keySet() {
5971
return cache.keys();
6072
}
6173

74+
public void incrementCountAndWeight(String key, String value) {
75+
count.inc();
76+
weight.inc(RamUsageEstimator.sizeOf(key) + RamUsageEstimator.sizeOf(value));
77+
}
78+
79+
/**
80+
* Get cache weight in bytes
81+
*/
82+
public Long getCount() {
83+
return count.count();
84+
}
85+
86+
/**
87+
* Get cache weight in bytes
88+
*/
89+
public Long getWeight() {
90+
return weight.count();
91+
}
92+
6293
static class IndexFieldMap {
63-
private ConcurrentHashMap<String, String> fieldTypeMap;
64-
private CounterMetric weight;
94+
private final ConcurrentHashMap<String, String> fieldTypeMap;
95+
96+
/**
97+
* Estimated memory consumption of fieldTypeMap in bytes
98+
*/
99+
private final CounterMetric weight;
65100

66101
IndexFieldMap() {
67102
fieldTypeMap = new ConcurrentHashMap<>();
@@ -72,11 +107,18 @@ public String get(String fieldName) {
72107
return fieldTypeMap.get(fieldName);
73108
}
74109

75-
public void putIfAbsent(String key, String value) {
110+
/**
111+
* Stores key, value if absent
112+
*
113+
* @return {@code true} if key was absent, else {@code false}
114+
*/
115+
public boolean putIfAbsent(String key, String value) {
76116
// Increment the weight only if the key value pair added to the Map
77117
if (fieldTypeMap.putIfAbsent(key, value) == null) {
78118
weight.inc(RamUsageEstimator.sizeOf(key) + RamUsageEstimator.sizeOf(value));
119+
return true;
79120
}
121+
return false;
80122
}
81123

82124
public long weight() {

src/main/java/org/opensearch/plugin/insights/core/service/categorizer/QueryShapeGenerator.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,22 @@ public class QueryShapeGenerator implements ClusterStateListener {
3838
static final String EMPTY_STRING = "";
3939
static final String ONE_SPACE_INDENT = " ";
4040
private final ClusterService clusterService;
41-
private final String NO_FIELD_TYPE_VALUE = "";
4241
private final IndicesFieldTypeCache indicesFieldTypeCache;
42+
private long cacheHitCount;
43+
private long cacheMissCount;
44+
45+
private final String NO_FIELD_TYPE_VALUE = "";
46+
public static final String HITS_STRING = "hits";
47+
public static final String MISSES_STRING = "misses";
48+
public static final String COUNT_STRING = "count";
49+
public static final String BYTES_STRING = "bytes";
4350

4451
public QueryShapeGenerator(ClusterService clusterService) {
4552
this.clusterService = clusterService;
4653
clusterService.addListener(this);
4754
this.indicesFieldTypeCache = new IndicesFieldTypeCache(clusterService.getSettings());
55+
this.cacheHitCount = 0;
56+
this.cacheMissCount = 0;
4857
}
4958

5059
public void clusterChanged(ClusterChangedEvent event) {
@@ -369,14 +378,20 @@ String getFieldType(String fieldName, Map<String, Object> propertiesAsMap, Index
369378
String fieldType = getFieldTypeFromCache(fieldName, index);
370379

371380
if (fieldType != null) {
381+
cacheHitCount += 1;
372382
return fieldType;
383+
} else {
384+
cacheMissCount += 1;
373385
}
374386

375387
// Retrieve field type from mapping and cache it if found
376388
fieldType = getFieldTypeFromProperties(fieldName, propertiesAsMap);
377389

378390
// Cache field type or NO_FIELD_TYPE_VALUE if not found
379-
indicesFieldTypeCache.getOrInitialize(index).putIfAbsent(fieldName, fieldType != null ? fieldType : NO_FIELD_TYPE_VALUE);
391+
fieldType = fieldType != null ? fieldType : NO_FIELD_TYPE_VALUE;
392+
if (indicesFieldTypeCache.getOrInitialize(index).putIfAbsent(fieldName, fieldType)) {
393+
indicesFieldTypeCache.incrementCountAndWeight(fieldName, fieldType);
394+
}
380395

381396
return fieldType;
382397
}
@@ -420,4 +435,22 @@ else if (currentMap.containsKey("type")) {
420435
String getFieldTypeFromCache(String fieldName, Index index) {
421436
return indicesFieldTypeCache.getOrInitialize(index).get(fieldName);
422437
}
438+
439+
/**
440+
* Get field type cache stats
441+
*
442+
* @return Map containing cache hit count, miss count, and byte stats
443+
*/
444+
public Map<String, Long> getFieldTypeCacheStats() {
445+
return Map.of(
446+
HITS_STRING,
447+
cacheHitCount,
448+
MISSES_STRING,
449+
cacheMissCount,
450+
COUNT_STRING,
451+
indicesFieldTypeCache.getCount(),
452+
BYTES_STRING,
453+
indicesFieldTypeCache.getWeight()
454+
);
455+
}
423456
}

src/main/java/org/opensearch/plugin/insights/rules/model/healthStats/QueryInsightsHealthStats.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,16 @@
88

99
package org.opensearch.plugin.insights.rules.model.healthStats;
1010

11+
import static org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator.BYTES_STRING;
12+
import static org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator.COUNT_STRING;
13+
import static org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator.HITS_STRING;
14+
import static org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator.MISSES_STRING;
15+
1116
import java.io.IOException;
17+
import java.util.List;
1218
import java.util.Map;
19+
import java.util.Objects;
20+
import org.opensearch.Version;
1321
import org.opensearch.core.common.io.stream.StreamInput;
1422
import org.opensearch.core.common.io.stream.StreamOutput;
1523
import org.opensearch.core.common.io.stream.Writeable;
@@ -26,10 +34,12 @@ public class QueryInsightsHealthStats implements ToXContentFragment, Writeable {
2634
private final ThreadPool.Info threadPoolInfo;
2735
private final int queryRecordsQueueSize;
2836
private final Map<MetricType, TopQueriesHealthStats> topQueriesHealthStats;
37+
private Map<String, Long> fieldTypeCacheStats;
2938

3039
private static final String THREAD_POOL_INFO = "ThreadPoolInfo";
3140
private static final String QUERY_RECORDS_QUEUE_SIZE = "QueryRecordsQueueSize";
3241
private static final String TOP_QUERIES_HEALTH_STATS = "TopQueriesHealthStats";
42+
private static final String FIELD_TYPE_CACHE_STATS = "FieldTypeCacheStats";
3343

3444
/**
3545
* Constructor to read QueryInsightsHealthStats from a StreamInput.
@@ -41,6 +51,9 @@ public QueryInsightsHealthStats(final StreamInput in) throws IOException {
4151
this.threadPoolInfo = new ThreadPool.Info(in);
4252
this.queryRecordsQueueSize = in.readInt();
4353
this.topQueriesHealthStats = in.readMap(MetricType::readFromStream, TopQueriesHealthStats::new);
54+
if (in.getVersion().onOrAfter(Version.V_2_19_0)) {
55+
this.fieldTypeCacheStats = in.readMap(StreamInput::readString, StreamInput::readLong);
56+
}
4457
}
4558

4659
/**
@@ -53,14 +66,16 @@ public QueryInsightsHealthStats(final StreamInput in) throws IOException {
5366
public QueryInsightsHealthStats(
5467
final ThreadPool.Info threadPoolInfo,
5568
final int queryRecordsQueueSize,
56-
final Map<MetricType, TopQueriesHealthStats> topQueriesHealthStats
69+
final Map<MetricType, TopQueriesHealthStats> topQueriesHealthStats,
70+
final Map<String, Long> fieldTypeCacheStats
5771
) {
5872
if (threadPoolInfo == null || topQueriesHealthStats == null) {
5973
throw new IllegalArgumentException("Parameters cannot be null");
6074
}
6175
this.threadPoolInfo = threadPoolInfo;
6276
this.queryRecordsQueueSize = queryRecordsQueueSize;
6377
this.topQueriesHealthStats = topQueriesHealthStats;
78+
this.fieldTypeCacheStats = Objects.requireNonNull(fieldTypeCacheStats, "fieldTypeCacheStats cannot be null");
6479
}
6580

6681
/**
@@ -87,6 +102,12 @@ public XContentBuilder toXContent(final XContentBuilder builder, final Params pa
87102
builder.endObject();
88103
}
89104
builder.endObject();
105+
// Write field type cache stats
106+
builder.startObject(FIELD_TYPE_CACHE_STATS);
107+
for (String key : List.of(HITS_STRING, MISSES_STRING, COUNT_STRING, BYTES_STRING)) {
108+
builder.field(key, fieldTypeCacheStats.getOrDefault(key, 0L));
109+
}
110+
builder.endObject();
90111
return builder;
91112
}
92113

@@ -105,6 +126,9 @@ public void writeTo(final StreamOutput out) throws IOException {
105126
MetricType::writeTo,
106127
(streamOutput, topQueriesHealthStats) -> topQueriesHealthStats.writeTo(out)
107128
);
129+
if (out.getVersion().onOrAfter(Version.V_2_19_0)) {
130+
out.writeMap(fieldTypeCacheStats, StreamOutput::writeString, StreamOutput::writeLong);
131+
}
108132
}
109133

110134
/**
@@ -133,4 +157,13 @@ public int getQueryRecordsQueueSize() {
133157
public Map<MetricType, TopQueriesHealthStats> getTopQueriesHealthStats() {
134158
return topQueriesHealthStats;
135159
}
160+
161+
/**
162+
* Get the field type cache stats.
163+
*
164+
* @return the field type cache stats
165+
*/
166+
public Map<String, Long> getFieldTypeCacheStats() {
167+
return fieldTypeCacheStats;
168+
}
136169
}

src/test/java/org/opensearch/plugin/insights/core/service/QueryInsightsServiceTests.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
import static org.mockito.Mockito.mock;
1313
import static org.mockito.Mockito.spy;
1414
import static org.mockito.Mockito.when;
15+
import static org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator.BYTES_STRING;
16+
import static org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator.COUNT_STRING;
17+
import static org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator.HITS_STRING;
18+
import static org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator.MISSES_STRING;
1519

1620
import java.util.List;
1721
import java.util.Map;
@@ -25,6 +29,7 @@
2529
import org.opensearch.core.xcontent.NamedXContentRegistry;
2630
import org.opensearch.plugin.insights.QueryInsightsTestUtils;
2731
import org.opensearch.plugin.insights.core.metrics.OperationalMetricsCounter;
32+
import org.opensearch.plugin.insights.core.service.categorizer.QueryShapeGenerator;
2833
import org.opensearch.plugin.insights.rules.model.GroupingType;
2934
import org.opensearch.plugin.insights.rules.model.MetricType;
3035
import org.opensearch.plugin.insights.rules.model.SearchQueryRecord;
@@ -59,8 +64,9 @@ public void setup() {
5964
"QueryInsightsHealthStatsTests",
6065
new ScalingExecutorBuilder(QueryInsightsSettings.QUERY_INSIGHTS_EXECUTOR, 1, 5, TimeValue.timeValueMinutes(5))
6166
);
67+
ClusterService clusterService = new ClusterService(settings, clusterSettings, threadPool);
6268
queryInsightsService = new QueryInsightsService(
63-
new ClusterService(settings, clusterSettings, threadPool),
69+
clusterService,
6470
threadPool,
6571
client,
6672
NoopMetricsRegistry.INSTANCE,
@@ -69,6 +75,7 @@ public void setup() {
6975
queryInsightsService.enableCollection(MetricType.LATENCY, true);
7076
queryInsightsService.enableCollection(MetricType.CPU, true);
7177
queryInsightsService.enableCollection(MetricType.MEMORY, true);
78+
queryInsightsService.setQueryShapeGenerator(new QueryShapeGenerator(clusterService));
7279
queryInsightsServiceSpy = spy(queryInsightsService);
7380

7481
MetricsRegistry metricsRegistry = mock(MetricsRegistry.class);
@@ -207,5 +214,12 @@ public void testGetHealthStats() {
207214
assertTrue(topQueriesHealthStatsMap.containsKey(MetricType.LATENCY));
208215
assertTrue(topQueriesHealthStatsMap.containsKey(MetricType.CPU));
209216
assertTrue(topQueriesHealthStatsMap.containsKey(MetricType.MEMORY));
217+
Map<String, Long> fieldTypeCacheStats = healthStats.getFieldTypeCacheStats();
218+
assertNotNull(fieldTypeCacheStats);
219+
assertEquals(4, fieldTypeCacheStats.size());
220+
assertTrue(fieldTypeCacheStats.containsKey(HITS_STRING));
221+
assertTrue(fieldTypeCacheStats.containsKey(MISSES_STRING));
222+
assertTrue(fieldTypeCacheStats.containsKey(COUNT_STRING));
223+
assertTrue(fieldTypeCacheStats.containsKey(BYTES_STRING));
210224
}
211225
}

src/test/java/org/opensearch/plugin/insights/rules/action/health_stats/HealthStatsNodeResponseTests.java

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public void setup() {
5353
this.healthStats = new QueryInsightsHealthStats(
5454
threadPool.info(QueryInsightsSettings.QUERY_INSIGHTS_EXECUTOR),
5555
10,
56+
new HashMap<>(),
5657
new HashMap<>()
5758
);
5859
}

0 commit comments

Comments
 (0)