aboutsummaryrefslogtreecommitdiffstats
path: root/node-repository
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@gmail.com>2021-03-18 12:44:48 +0100
committerJon Bratseth <bratseth@gmail.com>2021-03-18 12:44:48 +0100
commitf67aea9c3d29a413cf81455fe372f4662dcab1b5 (patch)
treeb5bca3b4dd96742f6f551378f5ba72fcab33e976 /node-repository
parent980a6180525633cf715d20cb965acc69474998d1 (diff)
Move to ClusterModel
Diffstat (limited to 'node-repository')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java25
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java96
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsResponse.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java74
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java29
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java (renamed from node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTargetTest.java)45
6 files changed, 131 insertions, 140 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java
index 9834f898113..9791aabf7b4 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java
@@ -59,9 +59,9 @@ public class Autoscaler {
}
private Advice autoscale(Application application, Cluster cluster, NodeList clusterNodes, Limits limits) {
- ClusterModel clusterModel = new ClusterModel(application, cluster, clusterNodes, metricsDb, nodeRepository);
+ ClusterModel clusterModel = new ClusterModel(application, cluster, clusterNodes, metricsDb, nodeRepository.clock());
- if ( ! clusterModel.isStable())
+ if ( ! clusterIsStable(clusterNodes, nodeRepository))
return Advice.none("Cluster change in progress");
if (scaledIn(clusterModel.scalingDuration(), cluster))
@@ -77,12 +77,7 @@ public class Autoscaler {
" nodes, but require from " + clusterNodes.size());
var currentAllocation = new AllocatableClusterResources(clusterNodes.asList(), nodeRepository, cluster.exclusive());
- var target = ResourceTarget.idealLoad(clusterModel.scalingDuration(),
- clusterModel.clusterTimeseries(),
- clusterModel.nodeTimeseries(),
- currentAllocation,
- application,
- nodeRepository.clock());
+ var target = ResourceTarget.idealLoad(clusterModel, currentAllocation);
Optional<AllocatableClusterResources> bestAllocation =
allocationOptimizer.findBestAllocation(target, currentAllocation, limits);
@@ -99,6 +94,20 @@ public class Autoscaler {
return Advice.scaleTo(bestAllocation.get().advertisedResources());
}
+ public static boolean clusterIsStable(NodeList clusterNodes, NodeRepository nodeRepository) {
+ // The cluster is processing recent changes
+ if (clusterNodes.stream().anyMatch(node -> node.status().wantToRetire() ||
+ node.allocation().get().membership().retired() ||
+ node.allocation().get().isRemovable()))
+ return false;
+
+ // A deployment is ongoing
+ if (nodeRepository.nodes().list(Node.State.reserved).owner(clusterNodes.first().get().allocation().get().owner()).size() > 0)
+ return false;
+
+ return true;
+ }
+
/** Returns true if both total real resources and total cost are similar */
public static boolean similar(ClusterResources a, ClusterResources b) {
return similar(a.cost(), b.cost(), costDifferenceWorthReallocation) &&
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java
index 32ddd52ca40..98955848d2d 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java
@@ -1,14 +1,14 @@
// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.autoscale;
-import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
-import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.applications.Application;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.applications.ScalingEvent;
+import java.time.Clock;
import java.time.Duration;
+import java.util.OptionalDouble;
/**
* A cluster with its associated metrics which allows prediction about its future behavior.
@@ -22,7 +22,8 @@ public class ClusterModel {
private final Cluster cluster;
private final NodeList nodes;
private final MetricsDb metricsDb;
- private final NodeRepository nodeRepository;
+ private final Clock clock;
+ private final Duration scalingDuration;
// Lazily initialized members
private ClusterNodesTimeseries nodeTimeseries = null;
@@ -32,14 +33,34 @@ public class ClusterModel {
Cluster cluster,
NodeList clusterNodes,
MetricsDb metricsDb,
- NodeRepository nodeRepository) {
+ Clock clock) {
this.application = application;
this.cluster = cluster;
this.nodes = clusterNodes;
this.metricsDb = metricsDb;
- this.nodeRepository = nodeRepository;
+ this.clock = clock;
+ this.scalingDuration = computeScalingDuration(cluster, clusterNodes);
}
+ /** For testing */
+ ClusterModel(Application application,
+ Cluster cluster,
+ Clock clock,
+ Duration scalingDuration,
+ ClusterTimeseries clusterTimeseries) {
+ this.application = application;
+ this.cluster = cluster;
+ this.nodes = null;
+ this.metricsDb = null;
+ this.clock = clock;
+
+ this.scalingDuration = scalingDuration;
+ this.clusterTimeseries = clusterTimeseries;
+ }
+
+ /** Returns the predicted duration of a rescaling of this cluster */
+ public Duration scalingDuration() { return scalingDuration; }
+
public ClusterNodesTimeseries nodeTimeseries() {
if (nodeTimeseries != null) return nodeTimeseries;
return nodeTimeseries = new ClusterNodesTimeseries(scalingDuration(), cluster, nodes, metricsDb);
@@ -50,26 +71,63 @@ public class ClusterModel {
return clusterTimeseries = metricsDb.getClusterTimeseries(application.id(), cluster.id());
}
- public boolean isStable() {
- return isStable(nodes, nodeRepository);
+ public double averageLoad(Resource resource) { return nodeTimeseries().averageLoad(resource); }
+
+ public double idealLoad(Resource resource) {
+ switch (resource) {
+ case cpu : return idealCpuLoad();
+ default : return resource.idealAverageLoad(); // TODO: Move here
+ }
}
- public static boolean isStable(NodeList clusterNodes, NodeRepository nodeRepository) {
- // The cluster is processing recent changes
- if (clusterNodes.stream().anyMatch(node -> node.status().wantToRetire() ||
- node.allocation().get().membership().retired() ||
- node.allocation().get().isRemovable()))
- return false;
+ /** Ideal cpu load must take the application traffic fraction into account */
+ private double idealCpuLoad() {
+ double queryCpuFraction = queryCpuFraction();
+
+ // What's needed to have headroom for growth during scale-up as a fraction of current resources?
+ double maxGrowthRate = clusterTimeseries().maxQueryGrowthRate(scalingDuration(), clock); // in fraction per minute of the current traffic
+ double growthRateHeadroom = 1 + maxGrowthRate * scalingDuration().toMinutes();
+ // Cap headroom at 10% above the historical observed peak
+ double fractionOfMax = clusterTimeseries().queryFractionOfMax(scalingDuration(), clock);
+ if (fractionOfMax != 0)
+ growthRateHeadroom = Math.min(growthRateHeadroom, 1 / fractionOfMax + 0.1);
+
+ // How much headroom is needed to handle sudden arrival of additional traffic due to another zone going down?
+ double maxTrafficShiftHeadroom = 10.0; // Cap to avoid extreme sizes from a current very small share
+ double trafficShiftHeadroom;
+ if (application.status().maxReadShare() == 0) // No traffic fraction data
+ trafficShiftHeadroom = 2.0; // assume we currently get half of the global share of traffic
+ else if (application.status().currentReadShare() == 0)
+ trafficShiftHeadroom = maxTrafficShiftHeadroom;
+ else
+ trafficShiftHeadroom = application.status().maxReadShare() / application.status().currentReadShare();
+ trafficShiftHeadroom = Math.min(trafficShiftHeadroom, maxTrafficShiftHeadroom);
+
+ // Assumptions: 1) Write load is not organic so we should not grow to handle more.
+ // (TODO: But allow applications to set their target write rate and size for that)
+ // 2) Write load does not change in BCP scenarios.
+ return queryCpuFraction * 1 / growthRateHeadroom * 1 / trafficShiftHeadroom * idealQueryCpuLoad() +
+ (1 - queryCpuFraction) * idealWriteCpuLoad();
+ }
- // A deployment is ongoing
- if (nodeRepository.nodes().list(Node.State.reserved).owner(clusterNodes.first().get().allocation().get().owner()).size() > 0)
- return false;
+ private double queryCpuFraction() {
+ OptionalDouble queryRate = clusterTimeseries().queryRate(scalingDuration(), clock);
+ OptionalDouble writeRate = clusterTimeseries().writeRate(scalingDuration(), clock);
+ if (queryRate.orElse(0) == 0 && writeRate.orElse(0) == 0) return queryCpuFraction(0.5);
+ return queryCpuFraction(queryRate.orElse(0) / (queryRate.orElse(0) + writeRate.orElse(0)));
+ }
- return true;
+ private double queryCpuFraction(double queryFraction) {
+ double relativeQueryCost = 9; // How much more expensive are queries than writes? TODO: Measure
+ double writeFraction = 1 - queryFraction;
+ return queryFraction * relativeQueryCost / (queryFraction * relativeQueryCost + writeFraction);
}
- /** The predicted duration of a rescaling of this cluster */
- public Duration scalingDuration() {
+ public static double idealQueryCpuLoad() { return Resource.cpu.idealAverageLoad(); }
+
+ public static double idealWriteCpuLoad() { return 0.95; }
+
+ private static Duration computeScalingDuration(Cluster cluster, NodeList nodes) {
int completedEventCount = 0;
Duration totalDuration = Duration.ZERO;
for (ScalingEvent event : cluster.scalingEvents()) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsResponse.java
index b6208cedc5e..b3cf6c1e962 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsResponse.java
@@ -103,7 +103,7 @@ public class MetricsResponse {
private boolean clusterIsStable(Node node, NodeList applicationNodes, NodeRepository nodeRepository) {
ClusterSpec cluster = node.allocation().get().membership().cluster();
- return ClusterModel.isStable(applicationNodes.cluster(cluster.id()), nodeRepository);
+ return Autoscaler.clusterIsStable(applicationNodes.cluster(cluster.id()), nodeRepository);
}
public static MetricsResponse empty() { return new MetricsResponse(List.of()); }
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java
index 9f6a4fc77cd..b1a1e86b08d 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java
@@ -51,18 +51,14 @@ public class ResourceTarget {
}
/** Create a target of achieving ideal load given a current load */
- public static ResourceTarget idealLoad(Duration scalingDuration,
- ClusterTimeseries clusterTimeseries,
- ClusterNodesTimeseries clusterNodesTimeseries,
- AllocatableClusterResources current,
- Application application,
- Clock clock) {
- return new ResourceTarget(nodeUsage(Resource.cpu, clusterNodesTimeseries.averageLoad(Resource.cpu), current)
- / idealCpuLoad(scalingDuration, clusterTimeseries, application, clock),
- nodeUsage(Resource.memory, clusterNodesTimeseries.averageLoad(Resource.memory), current)
- / Resource.memory.idealAverageLoad(),
- nodeUsage(Resource.disk, clusterNodesTimeseries.averageLoad(Resource.disk), current)
- / Resource.disk.idealAverageLoad(),
+ public static ResourceTarget idealLoad(ClusterModel clusterModel,
+ AllocatableClusterResources current) {
+ return new ResourceTarget(nodeUsage(Resource.cpu, clusterModel.averageLoad(Resource.cpu), current)
+ / clusterModel.idealLoad(Resource.cpu),
+ nodeUsage(Resource.memory, clusterModel.averageLoad(Resource.memory), current)
+ / clusterModel.idealLoad(Resource.memory),
+ nodeUsage(Resource.disk, clusterModel.averageLoad(Resource.disk), current)
+ / clusterModel.idealLoad(Resource.disk),
true);
}
@@ -74,58 +70,4 @@ public class ResourceTarget {
false);
}
- /** Ideal cpu load must take the application traffic fraction into account */
- public static double idealCpuLoad(Duration scalingDuration,
- ClusterTimeseries clusterTimeseries,
- Application application,
- Clock clock) {
- double queryCpuFraction = queryCpuFraction(clusterTimeseries, scalingDuration, clock);
-
- // What's needed to have headroom for growth during scale-up as a fraction of current resources?
- double maxGrowthRate = clusterTimeseries.maxQueryGrowthRate(scalingDuration, clock); // in fraction per minute of the current traffic
- double growthRateHeadroom = 1 + maxGrowthRate * scalingDuration.toMinutes();
- // Cap headroom at 10% above the historical observed peak
- double fractionOfMax = clusterTimeseries.queryFractionOfMax(scalingDuration, clock);
- if (fractionOfMax != 0)
- growthRateHeadroom = Math.min(growthRateHeadroom, 1 / fractionOfMax + 0.1);
-
- // How much headroom is needed to handle sudden arrival of additional traffic due to another zone going down?
- double maxTrafficShiftHeadroom = 10.0; // Cap to avoid extreme sizes from a current very small share
- double trafficShiftHeadroom;
- if (application.status().maxReadShare() == 0) // No traffic fraction data
- trafficShiftHeadroom = 2.0; // assume we currently get half of the global share of traffic
- else if (application.status().currentReadShare() == 0)
- trafficShiftHeadroom = maxTrafficShiftHeadroom;
- else
- trafficShiftHeadroom = application.status().maxReadShare() / application.status().currentReadShare();
- trafficShiftHeadroom = Math.min(trafficShiftHeadroom, maxTrafficShiftHeadroom);
-
- // Assumptions: 1) Write load is not organic so we should not grow to handle more.
- // (TODO: But allow applications to set their target write rate and size for that)
- // 2) Write load does not change in BCP scenarios.
- return queryCpuFraction * 1 / growthRateHeadroom * 1 / trafficShiftHeadroom * idealQueryCpuLoad() +
- (1 - queryCpuFraction) * idealWriteCpuLoad();
- }
-
- private static double queryCpuFraction(ClusterTimeseries clusterTimeseries, Duration scalingDuration, Clock clock) {
- OptionalDouble queryRate = clusterTimeseries.queryRate(scalingDuration, clock);
- OptionalDouble writeRate = clusterTimeseries.writeRate(scalingDuration, clock);
- if (queryRate.orElse(0) == 0 && writeRate.orElse(0) == 0) return queryCpuFraction(0.5);
- return queryCpuFraction(queryRate.orElse(0) / (queryRate.orElse(0) + writeRate.orElse(0)));
- }
-
- private static double queryCpuFraction(double queryFraction) {
- double relativeQueryCost = 9; // How much more expensive are queries than writes? TODO: Measure
- double writeFraction = 1 - queryFraction;
- return queryFraction * relativeQueryCost / (queryFraction * relativeQueryCost + writeFraction);
- }
-
- public static double idealQueryCpuLoad() { return Resource.cpu.idealAverageLoad(); }
-
- public static double idealWriteCpuLoad() { return 0.95; }
-
- public static double idealMemoryLoad() { return Resource.memory.idealAverageLoad(); }
-
- public static double idealDiskLoad() { return Resource.disk.idealAverageLoad(); }
-
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java
index af2f8d0c239..93d760fe00e 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java
@@ -69,9 +69,7 @@ public class ApplicationSerializer {
NodeList nodes = applicationNodes.not().retired().cluster(cluster.id());
if (nodes.isEmpty()) return;
ClusterResources currentResources = nodes.toResources();
- ClusterModel clusterModel = new ClusterModel(application, cluster, nodes, metricsDb, nodeRepository);
- var clusterNodesTimeseries = new ClusterNodesTimeseries(Duration.ofHours(1), cluster, nodes, metricsDb);
- var clusterTimeseries = metricsDb.getClusterTimeseries(application.id(), cluster.id());
+ ClusterModel clusterModel = new ClusterModel(application, cluster, nodes, metricsDb, nodeRepository.clock());
Cursor clusterObject = clustersObject.setObject(cluster.id().value());
clusterObject.setString("type", nodes.clusterSpec().type().name());
@@ -81,12 +79,12 @@ public class ApplicationSerializer {
if (cluster.shouldSuggestResources(currentResources))
cluster.suggestedResources().ifPresent(suggested -> toSlime(suggested.resources(), clusterObject.setObject("suggested")));
cluster.targetResources().ifPresent(target -> toSlime(target, clusterObject.setObject("target")));
- clusterUtilizationToSlime(application, clusterModel.scalingDuration(), clusterTimeseries, clusterNodesTimeseries, metricsDb.clock(), clusterObject.setObject("utilization"));
+ clusterUtilizationToSlime(clusterModel, clusterObject.setObject("utilization"));
scalingEventsToSlime(cluster.scalingEvents(), clusterObject.setArray("scalingEvents"));
clusterObject.setString("autoscalingStatus", cluster.autoscalingStatus());
clusterObject.setLong("scalingDuration", clusterModel.scalingDuration().toMillis());
- clusterObject.setDouble("maxQueryGrowthRate", clusterTimeseries.maxQueryGrowthRate(clusterModel.scalingDuration(), metricsDb.clock()));
- clusterObject.setDouble("currentQueryFractionOfMax", clusterTimeseries.queryFractionOfMax(clusterModel.scalingDuration(), metricsDb.clock()));
+ clusterObject.setDouble("maxQueryGrowthRate", clusterModel.clusterTimeseries().maxQueryGrowthRate(clusterModel.scalingDuration(), metricsDb.clock()));
+ clusterObject.setDouble("currentQueryFractionOfMax", clusterModel.clusterTimeseries().queryFractionOfMax(clusterModel.scalingDuration(), metricsDb.clock()));
}
private static void toSlime(ClusterResources resources, Cursor clusterResourcesObject) {
@@ -95,18 +93,13 @@ public class ApplicationSerializer {
NodeResourcesSerializer.toSlime(resources.nodeResources(), clusterResourcesObject.setObject("resources"));
}
- private static void clusterUtilizationToSlime(Application application,
- Duration scalingDuration,
- ClusterTimeseries clusterTimeseries,
- ClusterNodesTimeseries clusterNodesTimeseries,
- Clock clock,
- Cursor utilizationObject) {
- utilizationObject.setDouble("cpu", clusterNodesTimeseries.averageLoad(Resource.cpu));
- utilizationObject.setDouble("idealCpu", ResourceTarget.idealCpuLoad(scalingDuration, clusterTimeseries, application, clock));
- utilizationObject.setDouble("memory", clusterNodesTimeseries.averageLoad(Resource.memory));
- utilizationObject.setDouble("idealMemory", ResourceTarget.idealMemoryLoad());
- utilizationObject.setDouble("disk", clusterNodesTimeseries.averageLoad(Resource.disk));
- utilizationObject.setDouble("idealDisk", ResourceTarget.idealDiskLoad());
+ private static void clusterUtilizationToSlime(ClusterModel clusterModel, Cursor utilizationObject) {
+ utilizationObject.setDouble("cpu", clusterModel.averageLoad(Resource.cpu));
+ utilizationObject.setDouble("idealCpu", clusterModel.idealLoad(Resource.cpu));
+ utilizationObject.setDouble("memory", clusterModel.averageLoad(Resource.memory));
+ utilizationObject.setDouble("idealMemory", clusterModel.idealLoad(Resource.memory));
+ utilizationObject.setDouble("disk", clusterModel.averageLoad(Resource.disk));
+ utilizationObject.setDouble("idealDisk", clusterModel.idealLoad(Resource.disk));
}
private static void scalingEventsToSlime(List<ScalingEvent> scalingEvents, Cursor scalingEventsArray) {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTargetTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java
index 6f60de62f1f..70550b0a7c3 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTargetTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java
@@ -22,7 +22,7 @@ import static org.junit.Assert.assertEquals;
/**
* @author bratseth
*/
-public class ResourceTargetTest {
+public class ClusterModelTest {
private static final double delta = 0.001;
@@ -34,22 +34,16 @@ public class ResourceTargetTest {
application = application.with(cluster);
// No current traffic share: Ideal load is low but capped
- application = application.with(new Status(0.0, 1.0));
- assertEquals(0.131,
- ResourceTarget.idealCpuLoad(Duration.ofMinutes(10),
- timeseries(cluster,100, t -> t == 0 ? 10000.0 : 0.0, t -> 0.0, clock),
- application,
- clock),
- delta);
+ var model1 = new ClusterModel(application.with(new Status(0.0, 1.0)),
+ cluster, clock, Duration.ofMinutes(10),
+ timeseries(cluster,100, t -> t == 0 ? 10000.0 : 0.0, t -> 0.0, clock));
+ assertEquals(0.131, model1.idealLoad(Resource.cpu), delta);
// Almost no current traffic share: Ideal load is low but capped
- application = application.with(new Status(0.0001, 1.0));
- assertEquals(0.131,
- ResourceTarget.idealCpuLoad(Duration.ofMinutes(10),
- timeseries(cluster,100, t -> t == 0 ? 10000.0 : 0.0, t -> 0.0, clock),
- application,
- clock),
- delta);
+ var model2 = new ClusterModel(application.with(new Status(0.0001, 1.0)),
+ cluster, clock, Duration.ofMinutes(10),
+ timeseries(cluster,100, t -> t == 0 ? 10000.0 : 0.0, t -> 0.0, clock));
+ assertEquals(0.131, model2.idealLoad(Resource.cpu), delta);
}
@Test
@@ -61,21 +55,16 @@ public class ResourceTargetTest {
application = application.with(cluster);
// No current traffic: Ideal load is low but capped
- assertEquals(0.275,
- ResourceTarget.idealCpuLoad(Duration.ofMinutes(10),
- timeseries(cluster,100, t -> t == 0 ? 10000.0 : 0.0, t -> 0.0, clock),
- application,
- clock),
- delta);
+ var model1 = new ClusterModel(application,
+ cluster, clock, Duration.ofMinutes(10),
+ timeseries(cluster,100, t -> t == 0 ? 10000.0 : 0.0, t -> 0.0, clock));
+ assertEquals(0.275, model1.idealLoad(Resource.cpu), delta);
// Almost current traffic: Ideal load is low but capped
- application = application.with(new Status(0.0001, 1.0));
- assertEquals(0.04,
- ResourceTarget.idealCpuLoad(Duration.ofMinutes(10),
- timeseries(cluster,100, t -> t == 0 ? 10000.0 : 0.0001, t -> 0.0, clock),
- application,
- clock),
- delta);
+ var model2 = new ClusterModel(application.with(new Status(0.0001, 1.0)),
+ cluster, clock, Duration.ofMinutes(10),
+ timeseries(cluster,100, t -> t == 0 ? 10000.0 : 0.0001, t -> 0.0, clock));
+ assertEquals(0.275, model1.idealLoad(Resource.cpu), delta);
}
private Cluster cluster(NodeResources resources) {