summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorValerij Fredriksen <freva@users.noreply.github.com>2021-03-24 12:23:48 +0100
committerGitHub <noreply@github.com>2021-03-24 12:23:48 +0100
commita75edf56f1aa4279f2c46b6c32d28741f63386af (patch)
tree57f099a4d0f7841fd37bf5aa0876f2b0f9b9b805
parent6087d30ecabf7c80a9c1df8b74fe0b7873c9a70a (diff)
parent02f56edcb96817ed95462935f85428d6036d5514 (diff)
Merge pull request #17147 from vespa-engine/bratseth/application-stats
Bratseth/application stats
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepoStats.java111
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java16
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java13
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterNodesTimeseries.java24
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Load.java47
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsResponse.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricSnapshot.java20
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java12
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Resource.java30
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java41
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java21
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepoStatsTest.java104
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java21
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java10
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsV2MetricsFetcherTest.java18
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java19
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTester.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java8
20 files changed, 311 insertions, 221 deletions
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
index 7f563b876a7..0548bc7520f 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
@@ -316,4 +316,6 @@ public class NodeResources {
return value;
}
+ public static NodeResources zero() { return new NodeResources(0, 0, 0, 0); }
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepoStats.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepoStats.java
index e11a57f04df..ca18028ad5a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepoStats.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepoStats.java
@@ -1,11 +1,20 @@
// 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;
+import com.yahoo.collections.Pair;
+import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.NodeResources;
+import com.yahoo.config.provision.NodeType;
import com.yahoo.vespa.hosted.provision.autoscale.Load;
import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricSnapshot;
+import com.yahoo.vespa.hosted.provision.autoscale.NodeTimeseries;
import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -18,10 +27,12 @@ public class NodeRepoStats {
private final Load load;
private final Load activeLoad;
+ private final List<ApplicationStats> applicationStats;
- private NodeRepoStats(Load load, Load activeLoad) {
+ private NodeRepoStats(Load load, Load activeLoad, List<ApplicationStats> applicationStats) {
this.load = load;
this.activeLoad = activeLoad;
+ this.applicationStats = List.copyOf(applicationStats);
}
/**
@@ -33,36 +44,71 @@ public class NodeRepoStats {
/** Returns the current average utilization in this node repo over all active nodes. */
public Load activeLoad() { return activeLoad; }
+ /** Returns stats on each application, sorted by decreasing unutilized allocation measured in dollars per hour. */
+ public List<ApplicationStats> applicationStats() { return applicationStats; }
+
public static NodeRepoStats computeOver(NodeRepository nodeRepository) {
NodeList allNodes = nodeRepository.nodes().list();
+ List<NodeTimeseries> allNodeTimeseries = nodeRepository.metricsDb().getNodeTimeseries(Duration.ofHours(1), Set.of());
+
+ Pair<Load, Load> load = computeLoad(allNodes, allNodeTimeseries);
+ List<ApplicationStats> applicationStats = computeApplicationStats(allNodes, allNodeTimeseries);
+ return new NodeRepoStats(load.getFirst(), load.getSecond(), applicationStats);
+ }
- NodeResources totalActiveResources = new NodeResources(0, 0, 0, 0);
- double cpu = 0, memory = 0, disk = 0;
- for (var nodeTimeseries : nodeRepository.metricsDb().getNodeTimeseries(Duration.ofHours(1), Set.of())) {
+ private static Pair<Load, Load> computeLoad(NodeList allNodes, List<NodeTimeseries> allNodeTimeseries) {
+ NodeResources totalActiveResources = NodeResources.zero();
+ Load load = Load.zero();
+ for (var nodeTimeseries : allNodeTimeseries) {
Optional<Node> node = allNodes.node(nodeTimeseries.hostname());
if (node.isEmpty() || node.get().state() != Node.State.active) continue;
Optional<NodeMetricSnapshot> snapshot = nodeTimeseries.last();
if (snapshot.isEmpty()) continue;
- cpu += snapshot.get().cpu() * node.get().resources().vcpu();
- memory += snapshot.get().memory() * node.get().resources().memoryGb();
- disk += snapshot.get().disk() * node.get().resources().diskGb();
+ load = load.add(snapshot.get().load().multiply(node.get().resources()));
totalActiveResources = totalActiveResources.add(node.get().resources().justNumbers());
}
- NodeResources totalHostResources = new NodeResources(0, 0, 0, 0);
- for (var host : allNodes.hosts()) {
+ NodeResources totalHostResources = NodeResources.zero();
+ for (var host : allNodes.hosts())
totalHostResources = totalHostResources.add(host.resources().justNumbers());
+
+ return new Pair<>(load.divide(totalHostResources), load.divide(totalActiveResources));
+ }
+
+ private static List<ApplicationStats> computeApplicationStats(NodeList allNodes,
+ List<NodeTimeseries> allNodeTimeseries) {
+ List<ApplicationStats> applicationStats = new ArrayList<>();
+ Map<String, NodeMetricSnapshot> snapshotsByHost = byHost(allNodeTimeseries);
+ for (var applicationNodes : allNodes.state(Node.State.active)
+ .nodeType(NodeType.tenant)
+ .matching(node -> ! node.allocation().get().owner().instance().isTester())
+ .groupingBy(node -> node.allocation().get().owner()).entrySet()) {
+
+ NodeResources totalResources = NodeResources.zero();
+ NodeResources totalUtilizedResources = NodeResources.zero();
+ for (var node : applicationNodes.getValue()) {
+ var snapshot = snapshotsByHost.get(node.hostname());
+ if (snapshot == null) continue;
+
+ totalResources = totalResources.add(node.resources().justNumbers());
+ totalUtilizedResources = totalUtilizedResources.add(snapshot.load().scaled(node.resources().justNumbers()));
+ }
+ applicationStats.add(new ApplicationStats(applicationNodes.getKey(),
+ Load.byDividing(totalUtilizedResources, totalResources),
+ totalResources.cost(),
+ totalUtilizedResources.cost()));
}
+ Collections.sort(applicationStats);
+ return applicationStats;
+ }
- Load load = new Load(divide(cpu, totalHostResources.vcpu()),
- divide(memory, totalHostResources.memoryGb()),
- divide(disk, totalHostResources.diskGb()));
- Load activeLoad = new Load(divide(cpu, totalActiveResources.vcpu()),
- divide(memory, totalActiveResources.memoryGb()),
- divide(disk, totalActiveResources.diskGb()));
- return new NodeRepoStats(load, activeLoad);
+ private static Map<String, NodeMetricSnapshot> byHost(List<NodeTimeseries> allNodeTimeseries) {
+ Map<String, NodeMetricSnapshot> snapshots = new HashMap<>();
+ for (var nodeTimeseries : allNodeTimeseries)
+ nodeTimeseries.last().ifPresent(last -> snapshots.put(nodeTimeseries.hostname(), last));
+ return snapshots;
}
private static double divide(double a, double b) {
@@ -70,4 +116,37 @@ public class NodeRepoStats {
return a / b;
}
+ public static class ApplicationStats implements Comparable<ApplicationStats> {
+
+ private final ApplicationId id;
+ private final Load load;
+ private final double cost;
+ private final double utilizedCost;
+
+ public ApplicationStats(ApplicationId id, Load load, double cost, double utilizedCost) {
+ this.id = id;
+ this.load = load;
+ this.cost = cost;
+ this.utilizedCost = utilizedCost;
+ }
+
+ public ApplicationId id() { return id; }
+ public Load load() { return load; }
+
+ /** The standard cost of this application */
+ public double cost() { return cost; }
+
+ /** The amount of that cost which is currently utilized */
+ public double utilizedCost() { return utilizedCost; }
+
+ /** Cost - utilizedCost */
+ public double unutilizedCost() { return cost - utilizedCost; }
+
+ @Override
+ public int compareTo(NodeRepoStats.ApplicationStats other) {
+ return -Double.compare(this.unutilizedCost(), other.unutilizedCost());
+ }
+
+ }
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java
index a06ad89e299..79b09348d21 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java
@@ -96,18 +96,18 @@ public class AllocationOptimizer {
// Memory and disk: Scales with group size
// The fixed cost portion of cpu does not scale with changes to the node count
- double queryCpuPerGroup = fixedCpuCostFraction * target.nodeCpu() +
- (1 - fixedCpuCostFraction) * target.nodeCpu() * current.groupSize() / groupSize;
+ double queryCpuPerGroup = fixedCpuCostFraction * target.resources().vcpu() +
+ (1 - fixedCpuCostFraction) * target.resources().vcpu() * current.groupSize() / groupSize;
double queryCpu = queryCpuPerGroup * current.groups() / groups;
- double writeCpu = target.nodeCpu() * current.groupSize() / groupSize;
+ double writeCpu = target.resources().vcpu() * current.groupSize() / groupSize;
cpu = clusterModel.queryCpuFraction() * queryCpu + (1 - clusterModel.queryCpuFraction()) * writeCpu;
- memory = target.nodeMemory() * current.groupSize() / groupSize;
- disk = target.nodeDisk() * current.groupSize() / groupSize;
+ memory = target.resources().memoryGb() * current.groupSize() / groupSize;
+ disk = target.resources().diskGb() * current.groupSize() / groupSize;
}
else {
- cpu = target.nodeCpu() * current.nodes() / nodes;
- memory = target.nodeMemory();
- disk = target.nodeDisk();
+ cpu = target.resources().vcpu() * current.nodes() / nodes;
+ memory = target.resources().memoryGb();
+ disk = target.resources().diskGb();
}
// Combine the scaled resource values computed here
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 acf227e3de2..e3622c8f076 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
@@ -96,15 +96,10 @@ public class ClusterModel {
return queryFractionOfMax = clusterTimeseries().queryFractionOfMax(scalingDuration(), clock);
}
- public double averageLoad(Resource resource) { return nodeTimeseries().averageLoad(resource); }
-
- public double idealLoad(Resource resource) {
- switch (resource) {
- case cpu : return idealCpuLoad();
- case memory : return idealMemoryLoad;
- case disk : return idealDiskLoad;
- default : throw new IllegalStateException("No ideal load defined for " + resource);
- }
+ public Load averageLoad() { return nodeTimeseries().averageLoad(); }
+
+ public Load idealLoad() {
+ return new Load(idealCpuLoad(), idealMemoryLoad, idealDiskLoad);
}
/** Ideal cpu load must take the application traffic fraction into account */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterNodesTimeseries.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterNodesTimeseries.java
index c097abd8208..a7396f29d92 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterNodesTimeseries.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterNodesTimeseries.java
@@ -42,21 +42,17 @@ public class ClusterNodesTimeseries {
/** Returns the number of nodes measured in this */
public int nodesMeasured() { return timeseries.size(); }
- /** Returns the average load of this resource in this */
- public double averageLoad(Resource resource) {
- int measurementCount = timeseries.stream().mapToInt(m -> m.size()).sum();
- if (measurementCount == 0) return 0;
- double measurementSum = timeseries.stream().flatMap(m -> m.asList().stream()).mapToDouble(m -> value(resource, m)).sum();
- return measurementSum / measurementCount;
- }
-
- private double value(Resource resource, NodeMetricSnapshot snapshot) {
- switch (resource) {
- case cpu: return snapshot.cpu();
- case memory: return snapshot.memory();
- case disk: return snapshot.disk();
- default: throw new IllegalArgumentException("Got an unknown resource " + resource);
+ /** Returns the average load in this */
+ public Load averageLoad() {
+ Load total = Load.zero();
+ int count = 0;
+ for (var nodeTimeseries : timeseries) {
+ for (var snapshot : nodeTimeseries.asList()) {
+ total = total.add(snapshot.load());
+ count++;
+ }
}
+ return total.divide(count);
}
private List<NodeTimeseries> filter(List<NodeTimeseries> timeseries, Predicate<NodeMetricSnapshot> filter) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Load.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Load.java
index 1a13c1bb6d8..1e400bd2627 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Load.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Load.java
@@ -1,6 +1,8 @@
// 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.config.provision.NodeResources;
+
/**
* The load of a node or system, measured as fractions of max (1.0) in three dimensions.
*
@@ -20,11 +22,41 @@ public class Load {
public double memory() { return memory; }
public double disk() { return disk; }
+ public Load add(Load other) {
+ return new Load(cpu + other.cpu(), memory + other.memory(), disk + other.disk());
+ }
+
+ public Load multiply(NodeResources resources) {
+ return new Load(cpu * resources.vcpu(), memory * resources.memoryGb(), disk * resources.diskGb());
+ }
+
+ public Load multiply(double factor) {
+ return new Load(cpu * factor, memory * factor, disk * factor);
+ }
+
+ public Load divide(NodeResources resources) {
+ return new Load(divide(cpu, resources.vcpu()), divide(memory, resources.memoryGb()), divide(disk, resources.diskGb()));
+ }
+
+ public Load divide(Load divisor) {
+ return new Load(divide(cpu, divisor.cpu()), divide(memory, divisor.memory()), divide(disk, divisor.disk()));
+ }
+
+ public Load divide(double divisor) {
+ return new Load(divide(cpu, divisor), divide(memory, divisor), divide(disk, divisor));
+ }
+
+ public NodeResources scaled(NodeResources resources) {
+ return resources.withVcpu(cpu * resources.vcpu())
+ .withMemoryGb(memory * resources.memoryGb())
+ .withDiskGb(disk * resources.diskGb());
+ }
+
private double requireNormalized(double value, String name) {
if (Double.isNaN(value))
- throw new IllegalArgumentException(name + " must be a number between 0 and 1, but is NaN");
- if (value < 0 || value > 1)
- throw new IllegalArgumentException(name + " must be between 0 and 1, but is " + value);
+ throw new IllegalArgumentException(name + " must be a number but is NaN");
+ if (value < 0)
+ throw new IllegalArgumentException(name + " must be zero or lager, but is " + value);
return value;
}
@@ -35,4 +67,13 @@ public class Load {
public static Load zero() { return new Load(0, 0, 0); }
+ private static double divide(double a, double b) {
+ if (a == 0 && b == 0) return 0;
+ return a / b;
+ }
+
+ public static Load byDividing(NodeResources a, NodeResources b) {
+ return new Load(divide(a.vcpu(), b.vcpu()), divide(a.memoryGb(), b.memoryGb()), divide(a.diskGb(), b.diskGb()));
+ }
+
}
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 b3cf6c1e962..210388db7b8 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
@@ -67,9 +67,9 @@ public class MetricsResponse {
consumeServiceMetrics(nodeObject.field("services"), nodeValues);
nodeMetrics.add(new Pair<>(hostname, new NodeMetricSnapshot(at,
- Metric.cpu.from(nodeValues),
- Metric.memory.from(nodeValues),
- Metric.disk.from(nodeValues),
+ new Load(Metric.cpu.from(nodeValues),
+ Metric.memory.from(nodeValues),
+ Metric.disk.from(nodeValues)),
(long)Metric.generation.from(nodeValues),
Metric.inService.from(nodeValues) > 0,
clusterIsStable(node.get(), applicationNodes, nodeRepository),
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricSnapshot.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricSnapshot.java
index be9f7bd4819..6329d350642 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricSnapshot.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricSnapshot.java
@@ -12,21 +12,17 @@ public class NodeMetricSnapshot implements Comparable<NodeMetricSnapshot> {
private final Instant at;
- private final double cpu;
- private final double memory;
- private final double disk;
+ private final Load load;
private final long generation;
private final boolean inService;
private final boolean stable;
private final double queryRate;
- public NodeMetricSnapshot(Instant at, double cpu, double memory, double disk,
+ public NodeMetricSnapshot(Instant at, Load load,
long generation, boolean inService, boolean stable,
double queryRate) {
this.at = at;
- this.cpu = cpu;
- this.memory = memory;
- this.disk = disk;
+ this.load = load;
this.generation = generation;
this.inService = inService;
this.stable = stable;
@@ -34,9 +30,7 @@ public class NodeMetricSnapshot implements Comparable<NodeMetricSnapshot> {
}
public Instant at() { return at; }
- public double cpu() { return cpu; }
- public double memory() { return memory; }
- public double disk() { return disk; }
+ public Load load() { return load; }
/** Queries per second */
public double queryRate() { return queryRate; }
@@ -53,10 +47,8 @@ public class NodeMetricSnapshot implements Comparable<NodeMetricSnapshot> {
}
@Override
- public String toString() { return "metrics at " + at + ":" +
- " cpu: " + cpu +
- " memory: " + memory +
- " disk: " + disk +
+ public String toString() { return "metrics at " + at + ": " +
+ load +
" generation: " + generation +
" inService: " + inService +
" stable: " + stable +
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java
index 459a7919bbe..c933e16041a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDb.java
@@ -113,9 +113,9 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb {
TableWriter.Row row = writer.newRow(atMillis * 1000); // in microseconds
row.putStr(0, snapshot.getFirst());
// (1 is timestamp)
- row.putFloat(2, (float)snapshot.getSecond().cpu());
- row.putFloat(3, (float)snapshot.getSecond().memory());
- row.putFloat(4, (float)snapshot.getSecond().disk());
+ row.putFloat(2, (float)snapshot.getSecond().load().cpu());
+ row.putFloat(3, (float)snapshot.getSecond().load().memory());
+ row.putFloat(4, (float)snapshot.getSecond().load().disk());
row.putLong(5, snapshot.getSecond().generation());
row.putBool(6, snapshot.getSecond().inService());
row.putBool(7, snapshot.getSecond().stable());
@@ -358,9 +358,9 @@ public class QuestMetricsDb extends AbstractComponent implements MetricsDb {
if (hostnames.isEmpty() || hostnames.contains(hostname)) {
snapshots.put(hostname,
new NodeMetricSnapshot(Instant.ofEpochMilli(record.getTimestamp(1) / 1000),
- record.getFloat(2),
- record.getFloat(3),
- record.getFloat(4),
+ new Load(record.getFloat(2),
+ record.getFloat(3),
+ record.getFloat(4)),
record.getLong(5),
record.getBool(6),
record.getBool(7),
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Resource.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Resource.java
deleted file mode 100644
index c639ad1f779..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Resource.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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.config.provision.NodeResources;
-
-/**
- * A resource subject to autoscaling
- *
- * @author bratseth
- */
-public enum Resource {
-
- /** Cpu utilization ratio */
- cpu {
- double valueFrom(NodeResources resources) { return resources.vcpu(); }
- },
-
- /** Memory utilization ratio */
- memory {
- double valueFrom(NodeResources resources) { return resources.memoryGb(); }
- },
-
- /** Disk utilization ratio */
- disk {
- double valueFrom(NodeResources resources) { return resources.diskGb(); }
- };
-
- abstract double valueFrom(NodeResources resources);
-
-}
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 b1a1e86b08d..3b8b7f08684 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
@@ -1,6 +1,7 @@
// 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.config.provision.NodeResources;
import com.yahoo.vespa.hosted.provision.applications.Application;
import java.time.Clock;
@@ -18,56 +19,34 @@ public class ResourceTarget {
private final boolean adjustForRedundancy;
/** The target real resources per node, assuming the node assignment where this was decided */
- private final double cpu, memory, disk;
+ private final NodeResources resources;
- private ResourceTarget(double cpu, double memory, double disk, boolean adjustForRedundancy) {
- this.cpu = cpu;
- this.memory = memory;
- this.disk = disk;
+ private ResourceTarget(NodeResources resources, boolean adjustForRedundancy) {
+ this.resources = resources;
this.adjustForRedundancy = adjustForRedundancy;
}
/** Are the target resources given by this including redundancy or not */
public boolean adjustForRedundancy() { return adjustForRedundancy; }
- /** Returns the target cpu per node, in terms of the current allocation */
- public double nodeCpu() { return cpu; }
-
- /** Returns the target memory per node, in terms of the current allocation */
- public double nodeMemory() { return memory; }
-
- /** Returns the target disk per node, in terms of the current allocation */
- public double nodeDisk() { return disk; }
+ /** Returns the target resources per node in terms of the current allocation */
+ public NodeResources resources() { return resources; }
@Override
public String toString() {
- return "target " +
- (adjustForRedundancy ? "(with redundancy adjustment) " : "") +
- "[vcpu " + cpu + ", memoryGb " + memory + ", diskGb " + disk + "]";
- }
-
- private static double nodeUsage(Resource resource, double load, AllocatableClusterResources current) {
- return load * resource.valueFrom(current.realResources().nodeResources());
+ return "target " + resources + (adjustForRedundancy ? "(with redundancy adjustment) " : "");
}
/** Create a target of achieving ideal load given a current load */
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);
+ var loadAdjustment = clusterModel.averageLoad().divide(clusterModel.idealLoad());
+ return new ResourceTarget(loadAdjustment.scaled(current.realResources().nodeResources()), true);
}
/** Crete a target of preserving a current allocation */
public static ResourceTarget preserve(AllocatableClusterResources current) {
- return new ResourceTarget(current.realResources().nodeResources().vcpu(),
- current.realResources().nodeResources().memoryGb(),
- current.realResources().nodeResources().diskGb(),
- false);
+ return new ResourceTarget(current.realResources().nodeResources(), false);
}
}
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 176bf195f1f..692d757f41d 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
@@ -1,27 +1,18 @@
// 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.restapi;
-import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
-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 com.yahoo.vespa.hosted.provision.autoscale.ClusterModel;
-import com.yahoo.vespa.hosted.provision.autoscale.ClusterNodesTimeseries;
-import com.yahoo.vespa.hosted.provision.autoscale.ClusterTimeseries;
import com.yahoo.vespa.hosted.provision.autoscale.MetricsDb;
-import com.yahoo.vespa.hosted.provision.autoscale.Resource;
-import com.yahoo.vespa.hosted.provision.autoscale.ResourceTarget;
import java.net.URI;
-import java.time.Clock;
-import java.time.Duration;
-import java.util.Collection;
import java.util.List;
/**
@@ -94,12 +85,12 @@ public class ApplicationSerializer {
}
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));
+ utilizationObject.setDouble("cpu", clusterModel.averageLoad().cpu());
+ utilizationObject.setDouble("idealCpu", clusterModel.idealLoad().cpu());
+ utilizationObject.setDouble("memory", clusterModel.averageLoad().memory());
+ utilizationObject.setDouble("idealMemory", clusterModel.idealLoad().memory());
+ utilizationObject.setDouble("disk", clusterModel.averageLoad().disk());
+ utilizationObject.setDouble("idealDisk", clusterModel.idealLoad().disk());
}
private static void scalingEventsToSlime(List<ScalingEvent> scalingEvents, Cursor scalingEventsArray) {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepoStatsTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepoStatsTest.java
index 5d136016a71..44376fc103c 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepoStatsTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepoStatsTest.java
@@ -2,15 +2,24 @@
package com.yahoo.vespa.hosted.provision;
import com.yahoo.collections.Pair;
+import com.yahoo.component.Version;
+import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.NodeResources;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.hosted.provision.autoscale.Load;
import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricSnapshot;
+import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester;
import org.junit.Test;
import java.time.Duration;
import java.util.List;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* @author bratseth
@@ -24,6 +33,7 @@ public class NodeRepoStatsTest {
var tester = new NodeRepositoryTester();
assertLoad(Load.zero(), tester.nodeRepository().computeStats().load());
assertLoad(Load.zero(), tester.nodeRepository().computeStats().activeLoad());
+ assertTrue(tester.nodeRepository().computeStats().applicationStats().isEmpty());
}
@Test
@@ -34,33 +44,83 @@ public class NodeRepoStatsTest {
tester.addHost("host3", "small");
assertLoad(Load.zero(), tester.nodeRepository().computeStats().load());
assertLoad(Load.zero(), tester.nodeRepository().computeStats().activeLoad());
+ assertTrue(tester.nodeRepository().computeStats().applicationStats().isEmpty());
}
@Test
- public void testNodesAndMetrics() {
- var tester = new NodeRepositoryTester();
- tester.addHost("host1", "default");
- tester.addHost("host2", "default");
- tester.addHost("host3", "small");
- tester.addNode("node1", "host1", new NodeResources(0.2, 0.5, 4, 1));
- tester.addNode("node2", "host1", new NodeResources(0.3, 1.0, 8, 1));
- tester.addNode("node3", "host3", new NodeResources(0.3, 1.5, 12, 1));
- tester.setNodeState("node1", Node.State.active);
- tester.setNodeState("node2", Node.State.active);
- tester.setNodeState("node3", Node.State.active);
- assertLoad(Load.zero(), tester.nodeRepository().computeStats().load());
- assertLoad(Load.zero(), tester.nodeRepository().computeStats().activeLoad());
+ public void testStats() {
+ var hostResources = new NodeResources(10, 100, 1000, 10,
+ NodeResources.DiskSpeed.fast, NodeResources.StorageType.local);
+
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
+ tester.makeReadyHosts(10, hostResources).activateTenantHosts();
+
+ var app1 = ProvisioningTester.applicationId("app1");
+ var app2 = ProvisioningTester.applicationId("app2");
+ var app3 = ProvisioningTester.applicationId("app3");
+ var cluster1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("cluster1")).vespaVersion(Version.fromString("7")).build();
+ var cluster2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("cluster2")).vespaVersion(Version.fromString("7")).build();
+ var small = new NodeResources(1, 10, 100, 1, NodeResources.DiskSpeed.any);
+ var large = new NodeResources(2, 20, 200, 1);
- var before = tester.clock().instant();
- tester.clock().advance(Duration.ofMinutes(5));
+ // Deploy apps
+ var hostsApp1 = tester.prepare(app1, cluster1, Capacity.from(new ClusterResources(6, 1, small)));
+ hostsApp1.addAll(tester.prepare(app1, cluster2, Capacity.from(new ClusterResources(4, 1, large))));
+ tester.activate(app1, hostsApp1);
+ tester.activate(app2, cluster1, Capacity.from(new ClusterResources(8, 1, small)));
+ tester.activate(app3, cluster1, Capacity.from(new ClusterResources(5, 1, large)));
+
+ // Add metrics
+ double loadApp1Cluster1 = 0.2;
+ double loadApp1Cluster2 = 0.3;
+ double loadApp2 = 0.4;
+ double loadApp3 = 0.5;
var now = tester.clock().instant();
- tester.nodeRepository().metricsDb().addNodeMetrics(
- List.of(new Pair<>("node1", new NodeMetricSnapshot(before, 0, 0, 0, 1, true, true, 1.0)),
- new Pair<>("node1", new NodeMetricSnapshot(now, 0.5, 0.1, 0.8, 1, true, true, 1.0)),
- new Pair<>("node2", new NodeMetricSnapshot(now, 0.1, 0.8, 0.1, 1, true, true, 1.0)),
- new Pair<>("node3", new NodeMetricSnapshot(now, 1.0, 0.1, 0.2, 1, true, true, 1.0))));
- assertLoad(new Load(0.0860, 0.1000, 0.0256), tester.nodeRepository().computeStats().load());
- assertLoad(new Load(0.5375, 0.3333, 0.2667), tester.nodeRepository().computeStats().activeLoad());
+ for (Node node : tester.nodeRepository().nodes().list(Node.State.active)) {
+ double loadFactor;
+ var allocation = node.allocation().get();
+ if (allocation.owner().equals(app1)) {
+ if (allocation.membership().cluster().id().equals(cluster1.id()))
+ loadFactor = loadApp1Cluster1;
+ else
+ loadFactor = loadApp1Cluster2;
+ }
+ else if (allocation.owner().equals(app2)) {
+ loadFactor = loadApp2;
+ }
+ else {
+ loadFactor = loadApp3;
+ }
+ var snapshot = new NodeMetricSnapshot(now, new Load(1.0, 0.9, 0.8).multiply(loadFactor), 1, true, true, 1.0 );
+ tester.nodeRepository().metricsDb().addNodeMetrics(List.of(new Pair<>(node.hostname(), snapshot)));
+ }
+
+ var stats = tester.nodeRepository().computeStats();
+
+ assertLoad(new Load(0.6180,0.5562,0.4944), stats.load());
+ assertLoad(new Load(0.4682,0.4214,0.3745), stats.activeLoad());
+
+ var app1Stats = stats.applicationStats().get(0);
+ var app2Stats = stats.applicationStats().get(2);
+ var app3Stats = stats.applicationStats().get(1);
+
+ assertEquals(app1, app1Stats.id());
+ assertEquals(2.940, app1Stats.cost(), delta);
+ assertEquals(0.702, app1Stats.utilizedCost(), delta);
+ assertEquals(2.238, app1Stats.unutilizedCost(), delta);
+ assertLoad(new Load(0.2571, 0.2314, 0.2057), app1Stats.load());
+
+ assertEquals(app2, app2Stats.id());
+ assertEquals(1.680, app2Stats.cost(), delta);
+ assertEquals(0.624, app2Stats.utilizedCost(), delta);
+ assertEquals(1.056, app2Stats.unutilizedCost(), delta);
+ assertLoad(new Load(.40, 0.36, 0.32), app2Stats.load());
+
+ assertEquals(app3, app3Stats.id());
+ assertEquals(2.100, app3Stats.cost(), delta);
+ assertEquals(0.975, app3Stats.utilizedCost(), delta);
+ assertEquals(1.125, app3Stats.unutilizedCost(), delta);
+ assertLoad(new Load(0.5, 0.45, 0.40), app3Stats.load());
}
private static void assertLoad(Load expected, Load actual) {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java
index a5d8256439f..5fe6023e5af 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java
@@ -138,14 +138,12 @@ class AutoscalingTester {
for (int i = 0; i < count; i++) {
clock().advance(Duration.ofMinutes(5));
for (Node node : nodes) {
- float cpu = value * oneExtraNodeFactor;
- float memory = (float) ClusterModel.idealMemoryLoad * otherResourcesLoad * oneExtraNodeFactor;
- float disk = (float) ClusterModel.idealDiskLoad * otherResourcesLoad * oneExtraNodeFactor;
+ Load load = new Load(value,
+ ClusterModel.idealMemoryLoad * otherResourcesLoad,
+ ClusterModel.idealDiskLoad * otherResourcesLoad).multiply(oneExtraNodeFactor);
nodeMetricsDb().addNodeMetrics(List.of(new Pair<>(node.hostname(),
new NodeMetricSnapshot(clock().instant(),
- cpu,
- memory,
- disk,
+ load,
0,
true,
true,
@@ -174,11 +172,12 @@ class AutoscalingTester {
float cpu = (float) 0.2 * otherResourcesLoad * oneExtraNodeFactor;
float memory = value * oneExtraNodeFactor;
float disk = (float) ClusterModel.idealDiskLoad * otherResourcesLoad * oneExtraNodeFactor;
+ Load load = new Load(0.2 * otherResourcesLoad,
+ value,
+ ClusterModel.idealDiskLoad * otherResourcesLoad).multiply(oneExtraNodeFactor);
nodeMetricsDb().addNodeMetrics(List.of(new Pair<>(node.hostname(),
new NodeMetricSnapshot(clock().instant(),
- cpu,
- memory,
- disk,
+ load,
0,
true,
true,
@@ -199,9 +198,7 @@ class AutoscalingTester {
for (Node node : nodes) {
nodeMetricsDb().addNodeMetrics(List.of(new Pair<>(node.hostname(),
new NodeMetricSnapshot(clock().instant(),
- cpu,
- memory,
- disk,
+ new Load(cpu, memory, disk),
generation,
inService,
stable,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java
index 70550b0a7c3..550ecceee23 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java
@@ -37,13 +37,13 @@ public class ClusterModelTest {
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);
+ assertEquals(0.131, model1.idealLoad().cpu(), delta);
// Almost no current traffic share: Ideal load is low but capped
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);
+ assertEquals(0.131, model2.idealLoad().cpu(), delta);
}
@Test
@@ -58,13 +58,13 @@ public class ClusterModelTest {
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);
+ assertEquals(0.275, model1.idealLoad().cpu(), delta);
- // Almost current traffic: Ideal load is low but capped
+ // Almost no current traffic: Ideal load is low but capped
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);
+ assertEquals(0.040, model2.idealLoad().cpu(), delta);
}
private Cluster cluster(NodeResources resources) {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsV2MetricsFetcherTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsV2MetricsFetcherTest.java
index ef85e228cc7..5f1a36e7b56 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsV2MetricsFetcherTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/MetricsV2MetricsFetcherTest.java
@@ -51,14 +51,14 @@ public class MetricsV2MetricsFetcherTest {
assertEquals(2, values.size());
assertEquals("host-1.yahoo.com", values.get(0).getFirst());
- assertEquals(0.162, values.get(0).getSecond().cpu(), delta);
- assertEquals(0.231, values.get(0).getSecond().memory(), delta);
- assertEquals(0.820, values.get(0).getSecond().disk(), delta);
+ assertEquals(0.162, values.get(0).getSecond().load().cpu(), delta);
+ assertEquals(0.231, values.get(0).getSecond().load().memory(), delta);
+ assertEquals(0.820, values.get(0).getSecond().load().disk(), delta);
assertEquals("host-2.yahoo.com", values.get(1).getFirst());
- assertEquals(0.2, values.get(1).getSecond().cpu(), delta);
- assertEquals(0.0, values.get(1).getSecond().memory(), delta);
- assertEquals(0.4, values.get(1).getSecond().disk(), delta);
+ assertEquals(0.2, values.get(1).getSecond().load().cpu(), delta);
+ assertEquals(0.0, values.get(1).getSecond().load().memory(), delta);
+ assertEquals(0.4, values.get(1).getSecond().load().disk(), delta);
assertEquals(45.0, values.get(1).getSecond().queryRate(), delta);
}
@@ -69,9 +69,9 @@ public class MetricsV2MetricsFetcherTest {
httpClient.requestsReceived.get(1));
assertEquals(1, values.size());
assertEquals("host-3.yahoo.com", values.get(0).getFirst());
- assertEquals(0.10, values.get(0).getSecond().cpu(), delta);
- assertEquals(0.15, values.get(0).getSecond().memory(), delta);
- assertEquals(0.20, values.get(0).getSecond().disk(), delta);
+ assertEquals(0.10, values.get(0).getSecond().load().cpu(), delta);
+ assertEquals(0.15, values.get(0).getSecond().load().memory(), delta);
+ assertEquals(0.20, values.get(0).getSecond().load().disk(), delta);
assertEquals(3, values.get(0).getSecond().generation(), delta);
assertTrue(values.get(0).getSecond().stable());
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java
index 330038aa690..ae14b94e619 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsDbTest.java
@@ -40,9 +40,7 @@ public class NodeMetricsDbTest {
Collection<Pair<String, NodeMetricSnapshot>> values = new ArrayList<>();
for (int i = 0; i < 40; i++) {
values.add(new Pair<>(node0, new NodeMetricSnapshot(clock.instant(),
- 0.9f,
- 0.6f,
- 0.6f,
+ new Load(0.9, 0.6, 0.6),
0,
true,
false,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java
index f465a57d76a..34243f4548f 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/QuestMetricsDbTest.java
@@ -54,9 +54,9 @@ public class QuestMetricsDbTest {
assertEquals(1000, nodeTimeSeries1.get(0).size());
NodeMetricSnapshot snapshot = nodeTimeSeries1.get(0).asList().get(0);
assertEquals(startTime.plus(Duration.ofSeconds(1)), snapshot.at());
- assertEquals(0.1, snapshot.cpu(), delta);
- assertEquals(0.2, snapshot.memory(), delta);
- assertEquals(0.4, snapshot.disk(), delta);
+ assertEquals(0.1, snapshot.load().cpu(), delta);
+ assertEquals(0.2, snapshot.load().memory(), delta);
+ assertEquals(0.4, snapshot.load().disk(), delta);
assertEquals(1, snapshot.generation(), delta);
assertEquals(30, snapshot.queryRate(), delta);
@@ -234,10 +234,8 @@ public class QuestMetricsDbTest {
for (int i = 1; i <= countPerHost; i++) {
for (String host : hosts)
timeseries.add(new Pair<>(host, new NodeMetricSnapshot(clock.instant(),
- i * 0.1,
- i * 0.2,
- i * 0.4,
- i % 100,
+ new Load(i * 0.1, i * 0.2, i * 0.4),
+ i % 100,
true,
true,
30.0)));
@@ -260,11 +258,8 @@ public class QuestMetricsDbTest {
Collection<Pair<String, NodeMetricSnapshot>> timeseries = new ArrayList<>();
for (int i = 1; i <= countPerHost; i++) {
for (String host : hosts)
- timeseries.add(new Pair<>(host, new NodeMetricSnapshot(at,
- i * 0.1,
- i * 0.2,
- i * 0.4,
- i % 100,
+ timeseries.add(new Pair<>(host, new NodeMetricSnapshot(at, new Load(i * 0.1, i * 0.2, i * 0.4),
+ i % 100,
true,
false,
0.0)));
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTester.java
index 7313fc1797f..4ca12e4ab53 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTester.java
@@ -17,6 +17,7 @@ import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.autoscale.ClusterMetricSnapshot;
+import com.yahoo.vespa.hosted.provision.autoscale.Load;
import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricSnapshot;
import com.yahoo.vespa.hosted.provision.autoscale.MetricsDb;
import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder;
@@ -74,9 +75,7 @@ public class AutoscalingMaintainerTester {
for (Node node : nodes)
nodeRepository().metricsDb().addNodeMetrics(List.of(new Pair<>(node.hostname(),
new NodeMetricSnapshot(clock().instant(),
- cpu,
- mem,
- disk,
+ new Load(cpu, mem, disk),
generation,
true,
true,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java
index 29a93c463ce..7ab21946379 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java
@@ -17,10 +17,8 @@ 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.Cluster;
-import com.yahoo.vespa.hosted.provision.autoscale.ClusterModel;
+import com.yahoo.vespa.hosted.provision.autoscale.Load;
import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricSnapshot;
-import com.yahoo.vespa.hosted.provision.autoscale.MetricsDb;
-import com.yahoo.vespa.hosted.provision.autoscale.Resource;
import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder;
import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester;
import org.junit.Test;
@@ -124,9 +122,7 @@ public class ScalingSuggestionsMaintainerTest {
for (Node node : nodes)
nodeRepository.metricsDb().addNodeMetrics(List.of(new Pair<>(node.hostname(),
new NodeMetricSnapshot(nodeRepository.clock().instant(),
- cpu,
- memory,
- disk,
+ new Load(cpu, memory, disk),
generation,
true,
true,