Browse Source

Convert `ScriptStats` and `ScriptsCacheStats` objects to records #96400 (#96482)

Pablo Alcantar Morales 2 years ago
parent
commit
5d34fe5cd4

+ 10 - 22
server/src/main/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStats.java

@@ -38,6 +38,8 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Objects;
 
 
+import static org.elasticsearch.common.xcontent.ChunkedToXContentHelper.singleChunk;
+
 /**
 /**
  * Node statistics (dynamic, changes depending on when created).
  * Node statistics (dynamic, changes depending on when created).
  */
  */
@@ -104,7 +106,7 @@ public class NodeStats extends BaseNodeResponse implements ChunkedToXContent {
         transport = in.readOptionalWriteable(TransportStats::new);
         transport = in.readOptionalWriteable(TransportStats::new);
         http = in.readOptionalWriteable(HttpStats::new);
         http = in.readOptionalWriteable(HttpStats::new);
         breaker = in.readOptionalWriteable(AllCircuitBreakerStats::new);
         breaker = in.readOptionalWriteable(AllCircuitBreakerStats::new);
-        scriptStats = in.readOptionalWriteable(ScriptStats::new);
+        scriptStats = in.readOptionalWriteable(ScriptStats::read);
         scriptCacheStats = scriptStats != null ? scriptStats.toScriptCacheStats() : null;
         scriptCacheStats = scriptStats != null ? scriptStats.toScriptCacheStats() : null;
         discoveryStats = in.readOptionalWriteable(DiscoveryStats::new);
         discoveryStats = in.readOptionalWriteable(DiscoveryStats::new);
         ingestStats = in.readOptionalWriteable(IngestStats::read);
         ingestStats = in.readOptionalWriteable(IngestStats::read);
@@ -315,30 +317,16 @@ public class NodeStats extends BaseNodeResponse implements ChunkedToXContent {
             }),
             }),
 
 
             ifPresent(getThreadPool()).toXContentChunked(outerParams),
             ifPresent(getThreadPool()).toXContentChunked(outerParams),
-
-            Iterators.single((builder, params) -> {
-                ifPresent(getFs()).toXContent(builder, params);
-                return builder;
-            }),
-
+            singleChunk(ifPresent(getFs())),
             ifPresent(getTransport()).toXContentChunked(outerParams),
             ifPresent(getTransport()).toXContentChunked(outerParams),
             ifPresent(getHttp()).toXContentChunked(outerParams),
             ifPresent(getHttp()).toXContentChunked(outerParams),
-
-            Iterators.single((builder, params) -> {
-                ifPresent(getBreaker()).toXContent(builder, params);
-                ifPresent(getScriptStats()).toXContent(builder, params);
-                ifPresent(getDiscoveryStats()).toXContent(builder, params);
-                return builder;
-            }),
-
+            singleChunk(ifPresent(getBreaker())),
+            ifPresent(getScriptStats()).toXContentChunked(outerParams),
+            singleChunk(ifPresent(getDiscoveryStats())),
             ifPresent(getIngestStats()).toXContentChunked(outerParams),
             ifPresent(getIngestStats()).toXContentChunked(outerParams),
-
-            Iterators.single((builder, params) -> {
-                ifPresent(getAdaptiveSelectionStats()).toXContent(builder, params);
-                ifPresent(getScriptCacheStats()).toXContent(builder, params);
-                ifPresent(getIndexingPressureStats()).toXContent(builder, params);
-                return builder;
-            })
+            singleChunk(ifPresent(getAdaptiveSelectionStats())),
+            ifPresent(getScriptCacheStats()).toXContentChunked(outerParams),
+            singleChunk(ifPresent(getIndexingPressureStats()))
         );
         );
     }
     }
 
 

+ 8 - 0
server/src/main/java/org/elasticsearch/common/xcontent/ChunkedToXContentHelper.java

@@ -62,6 +62,14 @@ public enum ChunkedToXContentHelper {
         return Iterators.single(((builder, params) -> builder.field(name, value)));
         return Iterators.single(((builder, params) -> builder.field(name, value)));
     }
     }
 
 
+    public static Iterator<ToXContent> field(String name, long value) {
+        return Iterators.single(((builder, params) -> builder.field(name, value)));
+    }
+
+    public static Iterator<ToXContent> field(String name, String value) {
+        return Iterators.single(((builder, params) -> builder.field(name, value)));
+    }
+
     public static Iterator<ToXContent> array(String name, Iterator<? extends ToXContent> contents) {
     public static Iterator<ToXContent> array(String name, Iterator<? extends ToXContent> contents) {
         return Iterators.concat(ChunkedToXContentHelper.startArray(name), contents, ChunkedToXContentHelper.endArray());
         return Iterators.concat(ChunkedToXContentHelper.startArray(name), contents, ChunkedToXContentHelper.endArray());
     }
     }

+ 50 - 47
server/src/main/java/org/elasticsearch/script/ScriptCacheStats.java

@@ -12,45 +12,49 @@ import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.util.Maps;
 import org.elasticsearch.common.util.Maps;
-import org.elasticsearch.xcontent.ToXContentFragment;
-import org.elasticsearch.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.ChunkedToXContent;
+import org.elasticsearch.xcontent.ToXContent;
 
 
 import java.io.IOException;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Objects;
 
 
+import static org.elasticsearch.common.collect.Iterators.concat;
+import static org.elasticsearch.common.collect.Iterators.flatMap;
+import static org.elasticsearch.common.collect.Iterators.single;
+import static org.elasticsearch.common.xcontent.ChunkedToXContentHelper.endArray;
+import static org.elasticsearch.common.xcontent.ChunkedToXContentHelper.endObject;
+import static org.elasticsearch.common.xcontent.ChunkedToXContentHelper.field;
+import static org.elasticsearch.common.xcontent.ChunkedToXContentHelper.startArray;
+import static org.elasticsearch.common.xcontent.ChunkedToXContentHelper.startObject;
+import static org.elasticsearch.script.ScriptCacheStats.Fields.SCRIPT_CACHE_STATS;
+
 // This class is deprecated in favor of ScriptStats and ScriptContextStats
 // This class is deprecated in favor of ScriptStats and ScriptContextStats
-public class ScriptCacheStats implements Writeable, ToXContentFragment {
-    private final Map<String, ScriptStats> context;
-    private final ScriptStats general;
+public record ScriptCacheStats(Map<String, ScriptStats> context, ScriptStats general) implements Writeable, ChunkedToXContent {
 
 
     public ScriptCacheStats(Map<String, ScriptStats> context) {
     public ScriptCacheStats(Map<String, ScriptStats> context) {
-        this.context = Collections.unmodifiableMap(context);
-        this.general = null;
+        this(Collections.unmodifiableMap(context), null);
     }
     }
 
 
     public ScriptCacheStats(ScriptStats general) {
     public ScriptCacheStats(ScriptStats general) {
-        this.general = Objects.requireNonNull(general);
-        this.context = null;
+        this(null, Objects.requireNonNull(general));
     }
     }
 
 
-    public ScriptCacheStats(StreamInput in) throws IOException {
+    public static ScriptCacheStats read(StreamInput in) throws IOException {
         boolean isContext = in.readBoolean();
         boolean isContext = in.readBoolean();
         if (isContext == false) {
         if (isContext == false) {
-            general = new ScriptStats(in);
-            context = null;
-            return;
+            return new ScriptCacheStats(ScriptStats.read(in));
         }
         }
 
 
-        general = null;
         int size = in.readInt();
         int size = in.readInt();
         Map<String, ScriptStats> context = Maps.newMapWithExpectedSize(size);
         Map<String, ScriptStats> context = Maps.newMapWithExpectedSize(size);
         for (int i = 0; i < size; i++) {
         for (int i = 0; i < size; i++) {
             String name = in.readString();
             String name = in.readString();
-            context.put(name, new ScriptStats(in));
+            context.put(name, ScriptStats.read(in));
         }
         }
-        this.context = Collections.unmodifiableMap(context);
+        return new ScriptCacheStats(context);
     }
     }
 
 
     @Override
     @Override
@@ -70,37 +74,36 @@ public class ScriptCacheStats implements Writeable, ToXContentFragment {
     }
     }
 
 
     @Override
     @Override
-    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-        builder.startObject(Fields.SCRIPT_CACHE_STATS);
-        builder.startObject(Fields.SUM);
-        if (general != null) {
-            builder.field(ScriptStats.Fields.COMPILATIONS, general.getCompilations());
-            builder.field(ScriptStats.Fields.CACHE_EVICTIONS, general.getCacheEvictions());
-            builder.field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, general.getCompilationLimitTriggered());
-            builder.endObject().endObject();
-            return builder;
-        }
-
-        ScriptStats sum = sum();
-        builder.field(ScriptStats.Fields.COMPILATIONS, sum.getCompilations());
-        builder.field(ScriptStats.Fields.CACHE_EVICTIONS, sum.getCacheEvictions());
-        builder.field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, sum.getCompilationLimitTriggered());
-        builder.endObject();
-
-        builder.startArray(Fields.CONTEXTS);
-        for (String name : context.keySet().stream().sorted().toList()) {
-            ScriptStats stats = context.get(name);
-            builder.startObject();
-            builder.field(Fields.CONTEXT, name);
-            builder.field(ScriptStats.Fields.COMPILATIONS, stats.getCompilations());
-            builder.field(ScriptStats.Fields.CACHE_EVICTIONS, stats.getCacheEvictions());
-            builder.field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, stats.getCompilationLimitTriggered());
-            builder.endObject();
-        }
-        builder.endArray();
-        builder.endObject();
-
-        return builder;
+    public Iterator<? extends ToXContent> toXContentChunked(ToXContent.Params outerParams) {
+        return concat(
+            startObject(SCRIPT_CACHE_STATS),
+            startObject(Fields.SUM),
+            general != null
+                ? concat(
+                    field(ScriptStats.Fields.COMPILATIONS, general.getCompilations()),
+                    field(ScriptStats.Fields.CACHE_EVICTIONS, general.getCacheEvictions()),
+                    field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, general.getCompilationLimitTriggered()),
+                    endObject(),
+                    endObject()
+                )
+                : concat(single((builder, params) -> {
+                    var sum = sum();
+                    return builder.field(ScriptStats.Fields.COMPILATIONS, sum.getCompilations())
+                        .field(ScriptStats.Fields.CACHE_EVICTIONS, sum.getCacheEvictions())
+                        .field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, sum.getCompilationLimitTriggered())
+                        .endObject();
+                }), startArray(Fields.CONTEXTS), flatMap(context.keySet().stream().sorted().iterator(), ctx -> {
+                    var stats = context.get(ctx);
+                    return concat(
+                        startObject(),
+                        field(Fields.CONTEXT, ctx),
+                        field(ScriptStats.Fields.COMPILATIONS, stats.getCompilations()),
+                        field(ScriptStats.Fields.CACHE_EVICTIONS, stats.getCacheEvictions()),
+                        field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, stats.getCompilationLimitTriggered()),
+                        endObject()
+                    );
+                }), endArray(), endObject())
+        );
     }
     }
 
 
     /**
     /**

+ 41 - 18
server/src/main/java/org/elasticsearch/script/ScriptContextStats.java

@@ -18,13 +18,24 @@ import org.elasticsearch.xcontent.XContentBuilder;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.Objects;
 import java.util.Objects;
 
 
-public class ScriptContextStats implements Writeable, ToXContentFragment, Comparable<ScriptContextStats> {
-    private final String context;
-    private final long compilations;
-    private final TimeSeries compilationsHistory;
-    private final long cacheEvictions;
-    private final TimeSeries cacheEvictionsHistory;
-    private final long compilationLimitTriggered;
+/**
+ * Record object that holds stats information for the different script contexts in a node.
+ *
+ * @param context                    Context name.
+ * @param compilations               Total number of compilations.
+ * @param compilationsHistory        Historical information of the compilations of scripts in timeseries format.
+ * @param cacheEvictions             Total of evictions.
+ * @param cacheEvictionsHistory      Historical information of the evictions of scripts in timeseries format.
+ * @param compilationLimitTriggered  Total times that a limit of compilations that have reached the limit.
+ */
+public record ScriptContextStats(
+    String context,
+    long compilations,
+    TimeSeries compilationsHistory,
+    long cacheEvictions,
+    TimeSeries cacheEvictionsHistory,
+    long compilationLimitTriggered
+) implements Writeable, ToXContentFragment, Comparable<ScriptContextStats> {
 
 
     public ScriptContextStats(
     public ScriptContextStats(
         String context,
         String context,
@@ -32,19 +43,23 @@ public class ScriptContextStats implements Writeable, ToXContentFragment, Compar
         TimeSeries compilationsHistory,
         TimeSeries compilationsHistory,
         TimeSeries cacheEvictionsHistory
         TimeSeries cacheEvictionsHistory
     ) {
     ) {
-        this.context = Objects.requireNonNull(context);
-        this.compilations = compilationsHistory.total;
-        this.cacheEvictions = cacheEvictionsHistory.total;
-        this.compilationLimitTriggered = compilationLimitTriggered;
-        this.compilationsHistory = compilationsHistory;
-        this.cacheEvictionsHistory = cacheEvictionsHistory;
+        this(
+            Objects.requireNonNull(context),
+            compilationsHistory.total,
+            compilationsHistory,
+            cacheEvictionsHistory.total,
+            cacheEvictionsHistory,
+            compilationLimitTriggered
+        );
     }
     }
 
 
-    public ScriptContextStats(StreamInput in) throws IOException {
-        context = in.readString();
-        compilations = in.readVLong();
-        cacheEvictions = in.readVLong();
-        compilationLimitTriggered = in.readVLong();
+    public static ScriptContextStats read(StreamInput in) throws IOException {
+        var context = in.readString();
+        var compilations = in.readVLong();
+        var cacheEvictions = in.readVLong();
+        var compilationLimitTriggered = in.readVLong();
+        TimeSeries compilationsHistory;
+        TimeSeries cacheEvictionsHistory;
         if (in.getTransportVersion().onOrAfter(TransportVersion.V_8_1_0)) {
         if (in.getTransportVersion().onOrAfter(TransportVersion.V_8_1_0)) {
             compilationsHistory = new TimeSeries(in);
             compilationsHistory = new TimeSeries(in);
             cacheEvictionsHistory = new TimeSeries(in);
             cacheEvictionsHistory = new TimeSeries(in);
@@ -55,6 +70,14 @@ public class ScriptContextStats implements Writeable, ToXContentFragment, Compar
             compilationsHistory = new TimeSeries(compilations);
             compilationsHistory = new TimeSeries(compilations);
             cacheEvictionsHistory = new TimeSeries(cacheEvictions);
             cacheEvictionsHistory = new TimeSeries(cacheEvictions);
         }
         }
+        return new ScriptContextStats(
+            context,
+            compilations,
+            compilationsHistory,
+            cacheEvictions,
+            cacheEvictionsHistory,
+            compilationLimitTriggered
+        );
     }
     }
 
 
     @Override
     @Override

+ 1 - 1
server/src/main/java/org/elasticsearch/script/ScriptService.java

@@ -944,7 +944,7 @@ public class ScriptService implements Closeable, ClusterStateApplier, ScriptComp
                 ScriptCache cache = entry.getValue().get();
                 ScriptCache cache = entry.getValue().get();
                 contextStats.add(cache.stats(entry.getKey()));
                 contextStats.add(cache.stats(entry.getKey()));
             }
             }
-            return new ScriptStats(contextStats);
+            return ScriptStats.read(contextStats);
         }
         }
 
 
         ScriptCacheStats cacheStats() {
         ScriptCacheStats cacheStats() {

+ 100 - 66
server/src/main/java/org/elasticsearch/script/ScriptStats.java

@@ -9,46 +9,47 @@
 package org.elasticsearch.script;
 package org.elasticsearch.script;
 
 
 import org.elasticsearch.TransportVersion;
 import org.elasticsearch.TransportVersion;
+import org.elasticsearch.common.collect.Iterators;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.util.Maps;
 import org.elasticsearch.common.util.Maps;
-import org.elasticsearch.xcontent.ToXContentFragment;
-import org.elasticsearch.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.ChunkedToXContent;
+import org.elasticsearch.common.xcontent.ChunkedToXContentHelper;
+import org.elasticsearch.xcontent.ToXContent;
 
 
 import java.io.IOException;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
-
-public class ScriptStats implements Writeable, ToXContentFragment {
-    private final List<ScriptContextStats> contextStats;
-    private final long compilations;
-    private final long cacheEvictions;
-    private final long compilationLimitTriggered;
-    private final TimeSeries compilationsHistory;
-    private final TimeSeries cacheEvictionsHistory;
-
-    public ScriptStats(List<ScriptContextStats> contextStats) {
-        ArrayList<ScriptContextStats> ctxStats = new ArrayList<>(contextStats.size());
-        ctxStats.addAll(contextStats);
-        ctxStats.sort(ScriptContextStats::compareTo);
-        this.contextStats = Collections.unmodifiableList(ctxStats);
-        long compilations = 0;
-        long cacheEvictions = 0;
-        long compilationLimitTriggered = 0;
-        for (ScriptContextStats stats : contextStats) {
-            compilations += stats.getCompilations();
-            cacheEvictions += stats.getCacheEvictions();
-            compilationLimitTriggered += stats.getCompilationLimitTriggered();
-        }
-        this.compilations = compilations;
-        this.cacheEvictions = cacheEvictions;
-        this.compilationLimitTriggered = compilationLimitTriggered;
-        this.compilationsHistory = new TimeSeries(compilations);
-        this.cacheEvictionsHistory = new TimeSeries(cacheEvictions);
-    }
+import java.util.Objects;
+
+import static org.elasticsearch.common.collect.Iterators.single;
+import static org.elasticsearch.script.ScriptContextStats.Fields.COMPILATIONS_HISTORY;
+import static org.elasticsearch.script.ScriptStats.Fields.CACHE_EVICTIONS;
+import static org.elasticsearch.script.ScriptStats.Fields.COMPILATIONS;
+import static org.elasticsearch.script.ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED;
+import static org.elasticsearch.script.ScriptStats.Fields.CONTEXTS;
+import static org.elasticsearch.script.ScriptStats.Fields.SCRIPT_STATS;
+
+/**
+ * Record object that holds global statistics of the scripts in a node.
+ *
+ * @param contextStats               A list of different {@link ScriptContextStats}
+ * @param compilations               Total number of compilations.
+ * @param cacheEvictions             Total number of evictions.
+ * @param compilationLimitTriggered  Total number of times that the compilation time has been reached.
+ * @param compilationsHistory        Historical information of the compilations in timeseries format.
+ * @param cacheEvictionsHistory      Historical information of the evictions in timeseries format.
+ */
+public record ScriptStats(
+    List<ScriptContextStats> contextStats,
+    long compilations,
+    long cacheEvictions,
+    long compilationLimitTriggered,
+    TimeSeries compilationsHistory,
+    TimeSeries cacheEvictionsHistory
+) implements Writeable, ChunkedToXContent {
 
 
     public ScriptStats(
     public ScriptStats(
         long compilations,
         long compilations,
@@ -57,16 +58,37 @@ public class ScriptStats implements Writeable, ToXContentFragment {
         TimeSeries compilationsHistory,
         TimeSeries compilationsHistory,
         TimeSeries cacheEvictionsHistory
         TimeSeries cacheEvictionsHistory
     ) {
     ) {
-        this.contextStats = Collections.emptyList();
-        this.compilations = compilations;
-        this.cacheEvictions = cacheEvictions;
-        this.compilationLimitTriggered = compilationLimitTriggered;
-        this.compilationsHistory = compilationsHistory == null ? new TimeSeries(compilations) : compilationsHistory;
-        this.cacheEvictionsHistory = cacheEvictionsHistory == null ? new TimeSeries(cacheEvictions) : cacheEvictionsHistory;
+        this(
+            List.of(),
+            compilations,
+            cacheEvictions,
+            compilationLimitTriggered,
+            Objects.requireNonNullElseGet(compilationsHistory, () -> new TimeSeries(compilations)),
+            Objects.requireNonNullElseGet(cacheEvictionsHistory, () -> new TimeSeries(cacheEvictions))
+        );
     }
     }
 
 
-    public ScriptStats(ScriptContextStats context) {
-        this(
+    public static ScriptStats read(List<ScriptContextStats> contextStats) {
+        long compilations = 0;
+        long cacheEvictions = 0;
+        long compilationLimitTriggered = 0;
+        for (var stats : contextStats) {
+            compilations += stats.getCompilations();
+            cacheEvictions += stats.getCacheEvictions();
+            compilationLimitTriggered += stats.getCompilationLimitTriggered();
+        }
+        return new ScriptStats(
+            contextStats.stream().sorted(ScriptContextStats::compareTo).toList(),
+            compilations,
+            cacheEvictions,
+            compilationLimitTriggered,
+            new TimeSeries(compilations),
+            new TimeSeries(cacheEvictions)
+        );
+    }
+
+    public static ScriptStats read(ScriptContextStats context) {
+        return new ScriptStats(
             context.getCompilations(),
             context.getCompilations(),
             context.getCacheEvictions(),
             context.getCacheEvictions(),
             context.getCompilationLimitTriggered(),
             context.getCompilationLimitTriggered(),
@@ -75,7 +97,11 @@ public class ScriptStats implements Writeable, ToXContentFragment {
         );
         );
     }
     }
 
 
-    public ScriptStats(StreamInput in) throws IOException {
+    public static ScriptStats read(StreamInput in) throws IOException {
+        TimeSeries compilationsHistory;
+        TimeSeries cacheEvictionsHistory;
+        long compilations;
+        long cacheEvictions;
         if (in.getTransportVersion().onOrAfter(TransportVersion.V_8_1_0)) {
         if (in.getTransportVersion().onOrAfter(TransportVersion.V_8_1_0)) {
             compilationsHistory = new TimeSeries(in);
             compilationsHistory = new TimeSeries(in);
             cacheEvictionsHistory = new TimeSeries(in);
             cacheEvictionsHistory = new TimeSeries(in);
@@ -87,8 +113,16 @@ public class ScriptStats implements Writeable, ToXContentFragment {
             compilationsHistory = new TimeSeries(compilations);
             compilationsHistory = new TimeSeries(compilations);
             cacheEvictionsHistory = new TimeSeries(cacheEvictions);
             cacheEvictionsHistory = new TimeSeries(cacheEvictions);
         }
         }
-        compilationLimitTriggered = in.readVLong();
-        contextStats = in.readList(ScriptContextStats::new);
+        var compilationLimitTriggered = in.readVLong();
+        var contextStats = in.readList(ScriptContextStats::read);
+        return new ScriptStats(
+            contextStats,
+            compilations,
+            cacheEvictions,
+            compilationLimitTriggered,
+            compilationsHistory,
+            cacheEvictionsHistory
+        );
     }
     }
 
 
     @Override
     @Override
@@ -126,34 +160,34 @@ public class ScriptStats implements Writeable, ToXContentFragment {
         }
         }
         Map<String, ScriptStats> contexts = Maps.newMapWithExpectedSize(contextStats.size());
         Map<String, ScriptStats> contexts = Maps.newMapWithExpectedSize(contextStats.size());
         for (ScriptContextStats contextStats : contextStats) {
         for (ScriptContextStats contextStats : contextStats) {
-            contexts.put(contextStats.getContext(), new ScriptStats(contextStats));
+            contexts.put(contextStats.getContext(), ScriptStats.read(contextStats));
         }
         }
         return new ScriptCacheStats(contexts);
         return new ScriptCacheStats(contexts);
     }
     }
 
 
     @Override
     @Override
-    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-        builder.startObject(Fields.SCRIPT_STATS);
-        builder.field(Fields.COMPILATIONS, compilations);
-        builder.field(Fields.CACHE_EVICTIONS, cacheEvictions);
-        builder.field(Fields.COMPILATION_LIMIT_TRIGGERED, compilationLimitTriggered);
-        if (compilationsHistory != null && compilationsHistory.areTimingsEmpty() == false) {
-            builder.startObject(ScriptContextStats.Fields.COMPILATIONS_HISTORY);
-            compilationsHistory.toXContent(builder, params);
-            builder.endObject();
-        }
-        if (cacheEvictionsHistory != null && cacheEvictionsHistory.areTimingsEmpty() == false) {
-            builder.startObject(ScriptContextStats.Fields.COMPILATIONS_HISTORY);
-            cacheEvictionsHistory.toXContent(builder, params);
-            builder.endObject();
-        }
-        builder.startArray(Fields.CONTEXTS);
-        for (ScriptContextStats contextStats : contextStats) {
-            contextStats.toXContent(builder, params);
-        }
-        builder.endArray();
-        builder.endObject();
-        return builder;
+    public Iterator<? extends ToXContent> toXContentChunked(ToXContent.Params outerParams) {
+        return Iterators.concat(
+            ChunkedToXContentHelper.startObject(SCRIPT_STATS),
+            ChunkedToXContentHelper.field(COMPILATIONS, compilations),
+            ChunkedToXContentHelper.field(CACHE_EVICTIONS, cacheEvictions),
+            ChunkedToXContentHelper.field(COMPILATION_LIMIT_TRIGGERED, compilationLimitTriggered),
+            single((builder, params) -> {
+                if (compilationsHistory != null && compilationsHistory.areTimingsEmpty() == false) {
+                    builder.startObject(COMPILATIONS_HISTORY);
+                    compilationsHistory.toXContent(builder, params);
+                    builder.endObject();
+                }
+                if (cacheEvictionsHistory != null && cacheEvictionsHistory.areTimingsEmpty() == false) {
+                    builder.startObject(COMPILATIONS_HISTORY);
+                    cacheEvictionsHistory.toXContent(builder, params);
+                    builder.endObject();
+                }
+                return builder;
+            }),
+            ChunkedToXContentHelper.array(CONTEXTS, contextStats.iterator()),
+            ChunkedToXContentHelper.endObject()
+        );
     }
     }
 
 
     static final class Fields {
     static final class Fields {

+ 29 - 55
server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java

@@ -306,34 +306,8 @@ public class NodeStatsTests extends ESTestCase {
                 if (scriptStats == null) {
                 if (scriptStats == null) {
                     assertNull(deserializedScriptStats);
                     assertNull(deserializedScriptStats);
                 } else {
                 } else {
-                    List<ScriptContextStats> deserialized = deserializedScriptStats.getContextStats();
-                    long evictions = 0;
-                    long limited = 0;
-                    long compilations = 0;
-                    List<ScriptContextStats> stats = scriptStats.getContextStats();
-                    for (ScriptContextStats generatedStats : stats) {
-                        List<ScriptContextStats> maybeDeserStats = deserialized.stream()
-                            .filter(s -> s.getContext().equals(generatedStats.getContext()))
-                            .toList();
-
-                        assertEquals(1, maybeDeserStats.size());
-                        ScriptContextStats deserStats = maybeDeserStats.get(0);
-
-                        evictions += generatedStats.getCacheEvictions();
-                        assertEquals(generatedStats.getCacheEvictions(), deserStats.getCacheEvictions());
-
-                        limited += generatedStats.getCompilationLimitTriggered();
-                        assertEquals(generatedStats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered());
-
-                        compilations += generatedStats.getCompilations();
-                        assertEquals(generatedStats.getCompilations(), deserStats.getCompilations());
-
-                        assertEquals(generatedStats.getCacheEvictions(), deserStats.getCacheEvictions());
-                        assertEquals(generatedStats.getCompilations(), deserStats.getCompilations());
-                    }
-                    assertEquals(evictions, scriptStats.getCacheEvictions());
-                    assertEquals(limited, scriptStats.getCompilationLimitTriggered());
-                    assertEquals(compilations, scriptStats.getCompilations());
+                    assertEquals(scriptStats, deserializedScriptStats);
+                    assertNotSame(scriptStats, deserializedScriptStats);
                 }
                 }
                 DiscoveryStats discoveryStats = nodeStats.getDiscoveryStats();
                 DiscoveryStats discoveryStats = nodeStats.getDiscoveryStats();
                 DiscoveryStats deserializedDiscoveryStats = deserializedNodeStats.getDiscoveryStats();
                 DiscoveryStats deserializedDiscoveryStats = deserializedNodeStats.getDiscoveryStats();
@@ -476,33 +450,13 @@ public class NodeStatsTests extends ESTestCase {
                         assertEquals(aStats.responseTime, bStats.responseTime, 0.01);
                         assertEquals(aStats.responseTime, bStats.responseTime, 0.01);
                     });
                     });
                 }
                 }
-                ScriptCacheStats scriptCacheStats = nodeStats.getScriptCacheStats();
-                ScriptCacheStats deserializedScriptCacheStats = deserializedNodeStats.getScriptCacheStats();
+                var scriptCacheStats = nodeStats.getScriptCacheStats();
+                var deserializedScriptCacheStats = deserializedNodeStats.getScriptCacheStats();
                 if (scriptCacheStats == null) {
                 if (scriptCacheStats == null) {
                     assertNull(deserializedScriptCacheStats);
                     assertNull(deserializedScriptCacheStats);
                 } else if (deserializedScriptCacheStats.getContextStats() != null) {
                 } else if (deserializedScriptCacheStats.getContextStats() != null) {
-                    Map<String, ScriptStats> deserialized = deserializedScriptCacheStats.getContextStats();
-                    long evictions = 0;
-                    long limited = 0;
-                    long compilations = 0;
-                    Map<String, ScriptStats> stats = scriptCacheStats.getContextStats();
-                    for (String context : stats.keySet()) {
-                        ScriptStats deserStats = deserialized.get(context);
-                        ScriptStats generatedStats = stats.get(context);
-
-                        evictions += generatedStats.getCacheEvictions();
-                        assertEquals(generatedStats.getCacheEvictions(), deserStats.getCacheEvictions());
-
-                        limited += generatedStats.getCompilationLimitTriggered();
-                        assertEquals(generatedStats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered());
-
-                        compilations += generatedStats.getCompilations();
-                        assertEquals(generatedStats.getCompilations(), deserStats.getCompilations());
-                    }
-                    ScriptStats sum = deserializedScriptCacheStats.sum();
-                    assertEquals(evictions, sum.getCacheEvictions());
-                    assertEquals(limited, sum.getCompilationLimitTriggered());
-                    assertEquals(compilations, sum.getCompilations());
+                    assertEquals(scriptCacheStats, deserializedScriptCacheStats);
+                    assertNotSame(scriptCacheStats, deserializedScriptCacheStats);
                 }
                 }
             }
             }
         }
         }
@@ -527,12 +481,32 @@ public class NodeStatsTests extends ESTestCase {
     }
     }
 
 
     private static int expectedChunks(NodeStats nodeStats, NodeStatsLevel level) {
     private static int expectedChunks(NodeStats nodeStats, NodeStatsLevel level) {
-        return 5 // one per each chunkeable object
+        return 7 // one per each chunkeable object
             + expectedChunks(nodeStats.getHttp()) //
             + expectedChunks(nodeStats.getHttp()) //
             + expectedChunks(nodeStats.getIndices(), level) //
             + expectedChunks(nodeStats.getIndices(), level) //
             + expectedChunks(nodeStats.getTransport()) //
             + expectedChunks(nodeStats.getTransport()) //
             + expectedChunks(nodeStats.getIngestStats()) //
             + expectedChunks(nodeStats.getIngestStats()) //
-            + expectedChunks(nodeStats.getThreadPool());
+            + expectedChunks(nodeStats.getThreadPool()) //
+            + expectedChunks(nodeStats.getScriptStats()) //
+            + expectedChunks(nodeStats.getScriptCacheStats());
+    }
+
+    private static int expectedChunks(ScriptCacheStats scriptCacheStats) {
+        if (scriptCacheStats == null) return 0;
+
+        var chunks = 4;
+        if (scriptCacheStats.general() != null) {
+            chunks += 3;
+        } else {
+            chunks += 2;
+            chunks += scriptCacheStats.context().size() * 6;
+        }
+
+        return chunks;
+    }
+
+    private static int expectedChunks(ScriptStats scriptStats) {
+        return scriptStats == null ? 0 : 8 + scriptStats.contextStats().size();
     }
     }
 
 
     private static int expectedChunks(ThreadPoolStats threadPool) {
     private static int expectedChunks(ThreadPoolStats threadPool) {
@@ -904,7 +878,7 @@ public class NodeStatsTests extends ESTestCase {
                 contexts.add(context);
                 contexts.add(context);
                 stats.add(new ScriptContextStats(context, randomLongBetween(0, 1024), randomTimeSeries(), randomTimeSeries()));
                 stats.add(new ScriptContextStats(context, randomLongBetween(0, 1024), randomTimeSeries(), randomTimeSeries()));
             }
             }
-            scriptStats = new ScriptStats(stats);
+            scriptStats = ScriptStats.read(stats);
         }
         }
         ClusterApplierRecordingService.Stats timeTrackerStats;
         ClusterApplierRecordingService.Stats timeTrackerStats;
         if (randomBoolean()) {
         if (randomBoolean()) {

+ 96 - 0
server/src/test/java/org/elasticsearch/script/ScriptCacheStatsTests.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.script;
+
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xcontent.ToXContent;
+import org.elasticsearch.xcontent.XContentFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class ScriptCacheStatsTests extends ESTestCase {
+    public void testXContentChunkedWithGeneralMode() throws IOException {
+        var builder = XContentFactory.jsonBuilder().prettyPrint();
+        var contextStats = List.of(
+            new ScriptContextStats("context-1", 302, new TimeSeries(1000, 1001, 1002, 100), new TimeSeries(2000, 2001, 2002, 201)),
+            new ScriptContextStats("context-2", 3020, new TimeSeries(1000), new TimeSeries(2010))
+        );
+        var stats = ScriptStats.read(contextStats);
+        var scriptCacheStats = new ScriptCacheStats(stats);
+
+        builder.startObject();
+        for (var it = scriptCacheStats.toXContentChunked(ToXContent.EMPTY_PARAMS); it.hasNext();) {
+            it.next().toXContent(builder, ToXContent.EMPTY_PARAMS);
+        }
+        builder.endObject();
+
+        String expected = """
+            {
+              "script_cache" : {
+                "sum" : {
+                  "compilations" : 1100,
+                  "cache_evictions" : 2211,
+                  "compilation_limit_triggered" : 3322
+                }
+              }
+            }""";
+        assertThat(Strings.toString(builder), equalTo(expected));
+    }
+
+    public void testXContentChunkedWithContextMode() throws IOException {
+        var builder = XContentFactory.jsonBuilder().prettyPrint();
+        var scriptCacheStats = new ScriptCacheStats(
+            Map.of(
+                "context-1",
+                ScriptStats.read(
+                    new ScriptContextStats("context-1", 302, new TimeSeries(1000, 1001, 1002, 100), new TimeSeries(2000, 2001, 2002, 201))
+                ),
+                "context-2",
+                ScriptStats.read(new ScriptContextStats("context-2", 3020, new TimeSeries(1000), new TimeSeries(2010)))
+            )
+        );
+
+        builder.startObject();
+        for (var it = scriptCacheStats.toXContentChunked(ToXContent.EMPTY_PARAMS); it.hasNext();) {
+            it.next().toXContent(builder, ToXContent.EMPTY_PARAMS);
+        }
+        builder.endObject();
+
+        String expected = """
+            {
+              "script_cache" : {
+                "sum" : {
+                  "compilations" : 1100,
+                  "cache_evictions" : 2211,
+                  "compilation_limit_triggered" : 3322
+                },
+                "contexts" : [
+                  {
+                    "context" : "context-1",
+                    "compilations" : 100,
+                    "cache_evictions" : 201,
+                    "compilation_limit_triggered" : 302
+                  },
+                  {
+                    "context" : "context-2",
+                    "compilations" : 1000,
+                    "cache_evictions" : 2010,
+                    "compilation_limit_triggered" : 3020
+                  }
+                ]
+              }
+            }""";
+        assertThat(Strings.toString(builder), equalTo(expected));
+    }
+}

+ 15 - 14
server/src/test/java/org/elasticsearch/script/ScriptStatsTests.java

@@ -25,15 +25,18 @@ import java.util.function.Function;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.equalTo;
 
 
 public class ScriptStatsTests extends ESTestCase {
 public class ScriptStatsTests extends ESTestCase {
-    public void testXContent() throws IOException {
+    public void testXContentChunked() throws IOException {
         List<ScriptContextStats> contextStats = List.of(
         List<ScriptContextStats> contextStats = List.of(
             new ScriptContextStats("contextB", 302, new TimeSeries(1000, 1001, 1002, 100), new TimeSeries(2000, 2001, 2002, 201)),
             new ScriptContextStats("contextB", 302, new TimeSeries(1000, 1001, 1002, 100), new TimeSeries(2000, 2001, 2002, 201)),
             new ScriptContextStats("contextA", 3020, new TimeSeries(1000), new TimeSeries(2010))
             new ScriptContextStats("contextA", 3020, new TimeSeries(1000), new TimeSeries(2010))
         );
         );
-        ScriptStats stats = new ScriptStats(contextStats);
+        ScriptStats stats = ScriptStats.read(contextStats);
         final XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint();
         final XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint();
+
         builder.startObject();
         builder.startObject();
-        stats.toXContent(builder, ToXContent.EMPTY_PARAMS);
+        for (var it = stats.toXContentChunked(ToXContent.EMPTY_PARAMS); it.hasNext();) {
+            it.next().toXContent(builder, ToXContent.EMPTY_PARAMS);
+        }
         builder.endObject();
         builder.endObject();
 
 
         String expected = """
         String expected = """
@@ -135,6 +138,8 @@ public class ScriptStatsTests extends ESTestCase {
         ScriptContextStats stats = randomStats();
         ScriptContextStats stats = randomStats();
 
 
         ScriptContextStats deserStats = serDeser(TransportVersion.V_8_0_0, TransportVersion.V_7_16_0, stats);
         ScriptContextStats deserStats = serDeser(TransportVersion.V_8_0_0, TransportVersion.V_7_16_0, stats);
+        // Due to how the versions are handled by TimeSeries serialization, we cannot just simply assert that both object are
+        // equals but not the same
         assertEquals(stats.getCompilations(), deserStats.getCompilations());
         assertEquals(stats.getCompilations(), deserStats.getCompilations());
         assertEquals(stats.getCacheEvictions(), deserStats.getCacheEvictions());
         assertEquals(stats.getCacheEvictions(), deserStats.getCacheEvictions());
         assertEquals(stats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered());
         assertEquals(stats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered());
@@ -144,13 +149,12 @@ public class ScriptStatsTests extends ESTestCase {
         assertEquals(stats.getCacheEvictions(), deserStats.getCacheEvictionsHistory().total);
         assertEquals(stats.getCacheEvictions(), deserStats.getCacheEvictionsHistory().total);
 
 
         deserStats = serDeser(TransportVersion.V_8_0_0, TransportVersion.V_8_0_0, stats);
         deserStats = serDeser(TransportVersion.V_8_0_0, TransportVersion.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());
+        assertNotSame(stats, deserStats);
+        assertEquals(stats, deserStats);
 
 
         deserStats = serDeser(TransportVersion.V_8_1_0, TransportVersion.V_7_16_0, stats);
         deserStats = serDeser(TransportVersion.V_8_1_0, TransportVersion.V_7_16_0, stats);
+        // Due to how the versions are handled by TimeSeries serialization, we cannot just simply assert that both object are
+        // equals but not the same
         assertEquals(stats.getCompilations(), deserStats.getCompilations());
         assertEquals(stats.getCompilations(), deserStats.getCompilations());
         assertEquals(stats.getCacheEvictions(), deserStats.getCacheEvictions());
         assertEquals(stats.getCacheEvictions(), deserStats.getCacheEvictions());
         assertEquals(stats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered());
         assertEquals(stats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered());
@@ -158,11 +162,8 @@ public class ScriptStatsTests extends ESTestCase {
         assertEquals(new TimeSeries(stats.getCacheEvictionsHistory().total), deserStats.getCacheEvictionsHistory());
         assertEquals(new TimeSeries(stats.getCacheEvictionsHistory().total), deserStats.getCacheEvictionsHistory());
 
 
         deserStats = serDeser(TransportVersion.V_8_1_0, TransportVersion.V_8_1_0, stats);
         deserStats = serDeser(TransportVersion.V_8_1_0, TransportVersion.V_8_1_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());
+        assertNotSame(stats, deserStats);
+        assertEquals(stats, deserStats);
     }
     }
 
 
     public ScriptContextStats serDeser(TransportVersion outVersion, TransportVersion inVersion, ScriptContextStats stats)
     public ScriptContextStats serDeser(TransportVersion outVersion, TransportVersion inVersion, ScriptContextStats stats)
@@ -172,7 +173,7 @@ public class ScriptStatsTests extends ESTestCase {
             stats.writeTo(out);
             stats.writeTo(out);
             try (StreamInput in = out.bytes().streamInput()) {
             try (StreamInput in = out.bytes().streamInput()) {
                 in.setTransportVersion(inVersion);
                 in.setTransportVersion(inVersion);
-                return new ScriptContextStats(in);
+                return ScriptContextStats.read(in);
             }
             }
         }
         }
     }
     }