1
0
Эх сурвалжийг харах

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

Pablo Alcantar Morales 2 жил өмнө
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.Objects;
 
+import static org.elasticsearch.common.xcontent.ChunkedToXContentHelper.singleChunk;
+
 /**
  * Node statistics (dynamic, changes depending on when created).
  */
@@ -104,7 +106,7 @@ public class NodeStats extends BaseNodeResponse implements ChunkedToXContent {
         transport = in.readOptionalWriteable(TransportStats::new);
         http = in.readOptionalWriteable(HttpStats::new);
         breaker = in.readOptionalWriteable(AllCircuitBreakerStats::new);
-        scriptStats = in.readOptionalWriteable(ScriptStats::new);
+        scriptStats = in.readOptionalWriteable(ScriptStats::read);
         scriptCacheStats = scriptStats != null ? scriptStats.toScriptCacheStats() : null;
         discoveryStats = in.readOptionalWriteable(DiscoveryStats::new);
         ingestStats = in.readOptionalWriteable(IngestStats::read);
@@ -315,30 +317,16 @@ public class NodeStats extends BaseNodeResponse implements ChunkedToXContent {
             }),
 
             ifPresent(getThreadPool()).toXContentChunked(outerParams),
-
-            Iterators.single((builder, params) -> {
-                ifPresent(getFs()).toXContent(builder, params);
-                return builder;
-            }),
-
+            singleChunk(ifPresent(getFs())),
             ifPresent(getTransport()).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),
-
-            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)));
     }
 
+    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) {
         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.Writeable;
 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.util.Collections;
+import java.util.Iterator;
 import java.util.Map;
 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
-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) {
-        this.context = Collections.unmodifiableMap(context);
-        this.general = null;
+        this(Collections.unmodifiableMap(context), null);
     }
 
     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();
         if (isContext == false) {
-            general = new ScriptStats(in);
-            context = null;
-            return;
+            return new ScriptCacheStats(ScriptStats.read(in));
         }
 
-        general = null;
         int size = in.readInt();
         Map<String, ScriptStats> context = Maps.newMapWithExpectedSize(size);
         for (int i = 0; i < size; i++) {
             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
@@ -70,37 +74,36 @@ public class ScriptCacheStats implements Writeable, ToXContentFragment {
     }
 
     @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.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(
         String context,
@@ -32,19 +43,23 @@ public class ScriptContextStats implements Writeable, ToXContentFragment, Compar
         TimeSeries compilationsHistory,
         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)) {
             compilationsHistory = new TimeSeries(in);
             cacheEvictionsHistory = new TimeSeries(in);
@@ -55,6 +70,14 @@ public class ScriptContextStats implements Writeable, ToXContentFragment, Compar
             compilationsHistory = new TimeSeries(compilations);
             cacheEvictionsHistory = new TimeSeries(cacheEvictions);
         }
+        return new ScriptContextStats(
+            context,
+            compilations,
+            compilationsHistory,
+            cacheEvictions,
+            cacheEvictionsHistory,
+            compilationLimitTriggered
+        );
     }
 
     @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();
                 contextStats.add(cache.stats(entry.getKey()));
             }
-            return new ScriptStats(contextStats);
+            return ScriptStats.read(contextStats);
         }
 
         ScriptCacheStats cacheStats() {

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

@@ -9,46 +9,47 @@
 package org.elasticsearch.script;
 
 import org.elasticsearch.TransportVersion;
+import org.elasticsearch.common.collect.Iterators;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
 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.util.ArrayList;
-import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 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(
         long compilations,
@@ -57,16 +58,37 @@ public class ScriptStats implements Writeable, ToXContentFragment {
         TimeSeries compilationsHistory,
         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.getCacheEvictions(),
             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)) {
             compilationsHistory = new TimeSeries(in);
             cacheEvictionsHistory = new TimeSeries(in);
@@ -87,8 +113,16 @@ public class ScriptStats implements Writeable, ToXContentFragment {
             compilationsHistory = new TimeSeries(compilations);
             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
@@ -126,34 +160,34 @@ public class ScriptStats implements Writeable, ToXContentFragment {
         }
         Map<String, ScriptStats> contexts = Maps.newMapWithExpectedSize(contextStats.size());
         for (ScriptContextStats contextStats : contextStats) {
-            contexts.put(contextStats.getContext(), new ScriptStats(contextStats));
+            contexts.put(contextStats.getContext(), ScriptStats.read(contextStats));
         }
         return new ScriptCacheStats(contexts);
     }
 
     @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 {

+ 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) {
                     assertNull(deserializedScriptStats);
                 } 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 deserializedDiscoveryStats = deserializedNodeStats.getDiscoveryStats();
@@ -476,33 +450,13 @@ public class NodeStatsTests extends ESTestCase {
                         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) {
                     assertNull(deserializedScriptCacheStats);
                 } 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) {
-        return 5 // one per each chunkeable object
+        return 7 // one per each chunkeable object
             + expectedChunks(nodeStats.getHttp()) //
             + expectedChunks(nodeStats.getIndices(), level) //
             + expectedChunks(nodeStats.getTransport()) //
             + 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) {
@@ -904,7 +878,7 @@ public class NodeStatsTests extends ESTestCase {
                 contexts.add(context);
                 stats.add(new ScriptContextStats(context, randomLongBetween(0, 1024), randomTimeSeries(), randomTimeSeries()));
             }
-            scriptStats = new ScriptStats(stats);
+            scriptStats = ScriptStats.read(stats);
         }
         ClusterApplierRecordingService.Stats timeTrackerStats;
         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;
 
 public class ScriptStatsTests extends ESTestCase {
-    public void testXContent() throws IOException {
+    public void testXContentChunked() throws IOException {
         List<ScriptContextStats> contextStats = List.of(
             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))
         );
-        ScriptStats stats = new ScriptStats(contextStats);
+        ScriptStats stats = ScriptStats.read(contextStats);
         final XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint();
+
         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();
 
         String expected = """
@@ -135,6 +138,8 @@ public class ScriptStatsTests extends ESTestCase {
         ScriptContextStats stats = randomStats();
 
         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.getCacheEvictions(), deserStats.getCacheEvictions());
         assertEquals(stats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered());
@@ -144,13 +149,12 @@ public class ScriptStatsTests extends ESTestCase {
         assertEquals(stats.getCacheEvictions(), deserStats.getCacheEvictionsHistory().total);
 
         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);
+        // 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.getCacheEvictions(), deserStats.getCacheEvictions());
         assertEquals(stats.getCompilationLimitTriggered(), deserStats.getCompilationLimitTriggered());
@@ -158,11 +162,8 @@ public class ScriptStatsTests extends ESTestCase {
         assertEquals(new TimeSeries(stats.getCacheEvictionsHistory().total), deserStats.getCacheEvictionsHistory());
 
         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)
@@ -172,7 +173,7 @@ public class ScriptStatsTests extends ESTestCase {
             stats.writeTo(out);
             try (StreamInput in = out.bytes().streamInput()) {
                 in.setTransportVersion(inVersion);
-                return new ScriptContextStats(in);
+                return ScriptContextStats.read(in);
             }
         }
     }