diff --git a/server/src/main/java/org/elasticsearch/script/ScriptContextStats.java b/server/src/main/java/org/elasticsearch/script/ScriptContextStats.java index c0fc5d8b2493e..cf52d8d7a9347 100644 --- a/server/src/main/java/org/elasticsearch/script/ScriptContextStats.java +++ b/server/src/main/java/org/elasticsearch/script/ScriptContextStats.java @@ -8,6 +8,7 @@ package org.elasticsearch.script; +import org.elasticsearch.Version; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; @@ -20,14 +21,19 @@ public class ScriptContextStats implements Writeable, ToXContentFragment, Comparable { private final String context; private final long compilations; + private final TimeSeries compilationsHistory; private final long cacheEvictions; + private final TimeSeries cacheEvictionsHistory; private final long compilationLimitTriggered; - public ScriptContextStats(String context, long compilations, long cacheEvictions, long compilationLimitTriggered) { + public ScriptContextStats(String context, long compilations, long cacheEvictions, long compilationLimitTriggered, + TimeSeries compilationsHistory, TimeSeries cacheEvictionsHistory) { this.context = Objects.requireNonNull(context); this.compilations = compilations; this.cacheEvictions = cacheEvictions; this.compilationLimitTriggered = compilationLimitTriggered; + this.compilationsHistory = compilationsHistory; + this.cacheEvictionsHistory = cacheEvictionsHistory; } public ScriptContextStats(StreamInput in) throws IOException { @@ -35,6 +41,13 @@ public ScriptContextStats(StreamInput in) throws IOException { compilations = in.readVLong(); cacheEvictions = in.readVLong(); compilationLimitTriggered = in.readVLong(); + if (in.getVersion().onOrAfter(Version.V_8_0_0)) { + compilationsHistory = new TimeSeries(in); + cacheEvictionsHistory = new TimeSeries(in); + } else { + compilationsHistory = null; + cacheEvictionsHistory = null; + } } @Override @@ -43,6 +56,69 @@ public void writeTo(StreamOutput out) throws IOException { out.writeVLong(compilations); out.writeVLong(cacheEvictions); out.writeVLong(compilationLimitTriggered); + if (out.getVersion().onOrAfter(Version.V_8_0_0)) { + compilationsHistory.writeTo(out); + cacheEvictionsHistory.writeTo(out); + } + } + + public static class TimeSeries implements Writeable, ToXContentFragment { + public final long fiveMinutes; + public final long fifteenMinutes; + public final long twentyFourHours; + + public TimeSeries() { + this.fiveMinutes = 0; + this.fifteenMinutes = 0; + this.twentyFourHours = 0; + } + + public TimeSeries(long fiveMinutes, long fifteenMinutes, long twentyFourHours) { + assert fiveMinutes >= 0; + this.fiveMinutes = fiveMinutes; + assert fifteenMinutes >= fiveMinutes; + this.fifteenMinutes = fifteenMinutes; + assert twentyFourHours >= fifteenMinutes; + this.twentyFourHours = twentyFourHours; + } + + public TimeSeries(StreamInput in) throws IOException { + fiveMinutes = in.readVLong(); + fifteenMinutes = in.readVLong(); + twentyFourHours = in.readVLong(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(Fields.FIVE_MINUTES, fiveMinutes); + builder.field(Fields.FIFTEEN_MINUTES, fifteenMinutes); + builder.field(Fields.TWENTY_FOUR_HOURS, twentyFourHours); + return builder; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVLong(fiveMinutes); + out.writeVLong(fifteenMinutes); + out.writeVLong(twentyFourHours); + } + + public boolean isEmpty() { + return twentyFourHours == 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimeSeries that = (TimeSeries) o; + return fiveMinutes == that.fiveMinutes && fifteenMinutes == that.fifteenMinutes && twentyFourHours == that.twentyFourHours; + } + + @Override + public int hashCode() { + return Objects.hash(fiveMinutes, fifteenMinutes, twentyFourHours); + } } public String getContext() { @@ -53,10 +129,18 @@ public long getCompilations() { return compilations; } + public TimeSeries getCompilationsHistory() { + return compilationsHistory; + } + public long getCacheEvictions() { return cacheEvictions; } + public TimeSeries getCacheEvictionsHistory() { + return cacheEvictionsHistory; + } + public long getCompilationLimitTriggered() { return compilationLimitTriggered; } @@ -66,7 +150,22 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.startObject(); builder.field(Fields.CONTEXT, getContext()); builder.field(Fields.COMPILATIONS, getCompilations()); + + TimeSeries series = getCompilationsHistory(); + if (series != null && series.isEmpty() == false) { + builder.startObject(Fields.COMPILATIONS_HISTORY); + series.toXContent(builder, params); + builder.endObject(); + } + builder.field(Fields.CACHE_EVICTIONS, getCacheEvictions()); + series = getCacheEvictionsHistory(); + if (series != null && series.isEmpty() == false) { + builder.startObject(Fields.CACHE_EVICTIONS_HISTORY); + series.toXContent(builder, params); + builder.endObject(); + } + builder.field(Fields.COMPILATION_LIMIT_TRIGGERED, getCompilationLimitTriggered()); builder.endObject(); return builder; @@ -80,7 +179,12 @@ public int compareTo(ScriptContextStats o) { static final class Fields { static final String CONTEXT = "context"; static final String COMPILATIONS = "compilations"; + static final String COMPILATIONS_HISTORY = "compilations_history"; static final String CACHE_EVICTIONS = "cache_evictions"; + static final String CACHE_EVICTIONS_HISTORY = "cache_evictions_history"; static final String COMPILATION_LIMIT_TRIGGERED = "compilation_limit_triggered"; + static final String FIVE_MINUTES = "5m"; + static final String FIFTEEN_MINUTES = "15m"; + static final String TWENTY_FOUR_HOURS = "24h"; } } diff --git a/server/src/main/java/org/elasticsearch/script/ScriptMetrics.java b/server/src/main/java/org/elasticsearch/script/ScriptMetrics.java index 24ae237d62cc6..f853305c3cd45 100644 --- a/server/src/main/java/org/elasticsearch/script/ScriptMetrics.java +++ b/server/src/main/java/org/elasticsearch/script/ScriptMetrics.java @@ -32,6 +32,8 @@ public ScriptContextStats stats(String context) { context, compilationsMetric.count(), cacheEvictionsMetric.count(), - compilationLimitTriggered.count() + compilationLimitTriggered.count(), + new ScriptContextStats.TimeSeries(), + new ScriptContextStats.TimeSeries() ); }} diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java index 87ff0faa0937a..b6d90e9e15ed3 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java @@ -260,6 +260,9 @@ public void testSerialization() throws IOException { compilations += generatedStats.getCompilations(); assertEquals(generatedStats.getCompilations(), deserStats.getCompilations()); + + assertEquals(generatedStats.getCacheEvictionsHistory(), deserStats.getCacheEvictionsHistory()); + assertEquals(generatedStats.getCompilationsHistory(), deserStats.getCompilationsHistory()); } assertEquals(evictions, scriptStats.getCacheEvictions()); assertEquals(limited, scriptStats.getCompilationLimitTriggered()); @@ -555,11 +558,17 @@ public static NodeStats createNodeStats() { List stats = new ArrayList<>(numContents); HashSet contexts = new HashSet<>(); for (int i = 0; i < numContents; i++) { + long compile = randomLongBetween(0, 1024); + long eviction = randomLongBetween(0, 1024); + String context = randomValueOtherThanMany(contexts::contains, () -> randomAlphaOfLength(12)); + contexts.add(context); stats.add(new ScriptContextStats( - randomValueOtherThanMany(contexts::contains, () -> randomAlphaOfLength(12)), - randomLongBetween(0, 1024), + context, + compile, + eviction, randomLongBetween(0, 1024), - randomLongBetween(0, 1024)) + randomTimeSeries(), + randomTimeSeries()) ); } scriptStats = new ScriptStats(stats); @@ -663,6 +672,17 @@ public static NodeStats createNodeStats() { ingestStats, adaptiveSelectionStats, null); } + private static ScriptContextStats.TimeSeries randomTimeSeries() { + if (randomBoolean()) { + long day = randomLongBetween(0, 1024); + long fifteen = day >= 1 ? randomLongBetween(0, day) : 0; + long five = fifteen >= 1 ? randomLongBetween(0, fifteen) : 0; + return new ScriptContextStats.TimeSeries(five, fifteen, day); + } else { + return new ScriptContextStats.TimeSeries(); + } + } + private IngestStats.Stats getPipelineStats(List pipelineStats, String id) { return pipelineStats.stream().filter(p1 -> p1.getPipelineId().equals(id)).findFirst().map(p2 -> p2.getStats()).orElse(null); } diff --git a/server/src/test/java/org/elasticsearch/script/ScriptStatsTests.java b/server/src/test/java/org/elasticsearch/script/ScriptStatsTests.java index bf521e79f896f..1967837aa108e 100644 --- a/server/src/test/java/org/elasticsearch/script/ScriptStatsTests.java +++ b/server/src/test/java/org/elasticsearch/script/ScriptStatsTests.java @@ -8,22 +8,31 @@ package org.elasticsearch.script; +import org.elasticsearch.Version; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.test.ESTestCase; import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Locale; +import java.util.function.Function; import static org.hamcrest.Matchers.equalTo; public class ScriptStatsTests extends ESTestCase { public void testXContent() throws IOException { List contextStats = List.of( - new ScriptContextStats("contextB", 100, 201, 302), - new ScriptContextStats("contextA", 1000, 2010, 3020) + new ScriptContextStats("contextB", 100, 201, 302, + new ScriptContextStats.TimeSeries(1000, 1001, 1002), + new ScriptContextStats.TimeSeries(2000, 2001, 2002) + ), + new ScriptContextStats("contextA", 1000, 2010, 3020, null, new ScriptContextStats.TimeSeries(0, 0, 0)) ); ScriptStats stats = new ScriptStats(contextStats); final XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint(); @@ -46,7 +55,17 @@ public void testXContent() throws IOException { " {\n" + " \"context\" : \"contextB\",\n" + " \"compilations\" : 100,\n" + + " \"compilations_history\" : {\n" + + " \"5m\" : 1000,\n" + + " \"15m\" : 1001,\n" + + " \"24h\" : 1002\n" + + " },\n" + " \"cache_evictions\" : 201,\n" + + " \"cache_evictions_history\" : {\n" + + " \"5m\" : 2000,\n" + + " \"15m\" : 2001,\n" + + " \"24h\" : 2002\n" + + " },\n" + " \"compilation_limit_triggered\" : 302\n" + " }\n" + " ]\n" + @@ -54,4 +73,122 @@ public void testXContent() throws IOException { "}"; assertThat(Strings.toString(builder), equalTo(expected)); } + + public void testSerializeEmptyTimeSeries() throws IOException { + ScriptContextStats.TimeSeries empty = new ScriptContextStats.TimeSeries(); + ScriptContextStats stats = new ScriptContextStats("c", 1111, 2222, 3333, null, empty); + + XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint(); + stats.toXContent(builder, ToXContent.EMPTY_PARAMS); + + String expected = + "{\n" + + " \"context\" : \"c\",\n" + + " \"compilations\" : 1111,\n" + + " \"cache_evictions\" : 2222,\n" + + " \"compilation_limit_triggered\" : 3333\n" + + "}"; + + assertThat(Strings.toString(builder), equalTo(expected)); + } + + public void testSerializeTimeSeries() throws IOException { + Function mkContextStats = + (ts) -> new ScriptContextStats("c", 1111, 2222, 3333, null, ts); + + ScriptContextStats.TimeSeries series = new ScriptContextStats.TimeSeries(0, 0, 5); + String format = + "{\n" + + " \"context\" : \"c\",\n" + + " \"compilations\" : 1111,\n" + + " \"cache_evictions\" : 2222,\n" + + " \"cache_evictions_history\" : {\n" + + " \"5m\" : %d,\n" + + " \"15m\" : %d,\n" + + " \"24h\" : %d\n" + + " },\n" + + " \"compilation_limit_triggered\" : 3333\n" + + "}"; + + XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint(); + mkContextStats.apply(series).toXContent(builder, ToXContent.EMPTY_PARAMS); + + assertThat(Strings.toString(builder), equalTo(String.format(Locale.ROOT, format, 0, 0, 5))); + + series = new ScriptContextStats.TimeSeries(0, 7, 1234); + builder = XContentFactory.jsonBuilder().prettyPrint(); + mkContextStats.apply(series).toXContent(builder, ToXContent.EMPTY_PARAMS); + assertThat(Strings.toString(builder), equalTo(String.format(Locale.ROOT, format, 0, 7, 1234))); + + series = new ScriptContextStats.TimeSeries(123, 456, 789); + builder = XContentFactory.jsonBuilder().prettyPrint(); + mkContextStats.apply(series).toXContent(builder, ToXContent.EMPTY_PARAMS); + assertThat(Strings.toString(builder), equalTo(String.format(Locale.ROOT, format, 123, 456, 789))); + } + + public void testTimeSeriesAssertions() { + expectThrows(AssertionError.class, () -> new ScriptContextStats.TimeSeries(-1, 1, 2)); + expectThrows(AssertionError.class, () -> new ScriptContextStats.TimeSeries(1, 0, 2)); + expectThrows(AssertionError.class, () -> new ScriptContextStats.TimeSeries(1, 3, 2)); + } + + public void testTimeSeriesIsEmpty() { + assertTrue((new ScriptContextStats.TimeSeries(0, 0, 0)).isEmpty()); + long day = randomLongBetween(1, 1024); + long fifteen = day >= 1 ? randomLongBetween(0, day) : 0; + long five = fifteen >= 1 ? randomLongBetween(0, fifteen) : 0; + assertFalse((new ScriptContextStats.TimeSeries(five, fifteen, day)).isEmpty()); + } + + public void testTimeSeriesSerialization() throws IOException { + ScriptContextStats stats = randomStats(); + + ScriptContextStats deserStats = serDeser(Version.V_8_0_0, Version.V_7_16_0, stats); + assertEquals(stats.getCompilations(), deserStats.getCompilations()); + assertEquals(stats.getCacheEvictions(), deserStats.getCacheEvictions()); + assertEquals(stats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered()); + assertNull(deserStats.getCompilationsHistory()); + assertNull(deserStats.getCacheEvictionsHistory()); + + deserStats = serDeser(Version.V_8_0_0, Version.V_8_0_0, stats); + assertEquals(stats.getCompilations(), deserStats.getCompilations()); + assertEquals(stats.getCacheEvictions(), deserStats.getCacheEvictions()); + assertEquals(stats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered()); + assertEquals(stats.getCompilationsHistory(), deserStats.getCompilationsHistory()); + assertEquals(stats.getCacheEvictionsHistory(), deserStats.getCacheEvictionsHistory()); + } + + public ScriptContextStats serDeser(Version outVersion, Version inVersion, ScriptContextStats stats) throws IOException { + try (BytesStreamOutput out = new BytesStreamOutput()) { + out.setVersion(outVersion); + stats.writeTo(out); + try (StreamInput in = out.bytes().streamInput()) { + in.setVersion(inVersion); + return new ScriptContextStats(in); + } + } + } + + public ScriptContextStats randomStats() { + long[] histStats = {randomLongBetween(0, 2048), randomLongBetween(0, 2048)}; + List timeSeries = new ArrayList<>(); + for (int j = 0; j < 2; j++) { + if (randomBoolean() && histStats[j] > 0) { + long day = randomLongBetween(0, histStats[j]); + long fifteen = day >= 1 ? randomLongBetween(0, day) : 0; + long five = fifteen >= 1 ? randomLongBetween(0, fifteen) : 0; + timeSeries.add(new ScriptContextStats.TimeSeries(five, fifteen, day)); + } else { + timeSeries.add(new ScriptContextStats.TimeSeries()); + } + } + return new ScriptContextStats( + randomAlphaOfLength(15), + histStats[0], + histStats[1], + randomLongBetween(0, 1024), + timeSeries.get(0), + timeSeries.get(1) + ); + } }