summaryrefslogtreecommitdiffstats
path: root/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaling.java
blob: 0c86108b36cf6137355610201aed85d2231e1780 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package com.yahoo.vespa.hosted.provision.autoscale;

import com.yahoo.config.provision.ClusterResources;
import io.questdb.Metrics;

import java.time.Instant;
import java.util.Objects;
import java.util.Optional;

/**
 * An autoscaling conclusion and the context that led to it.
 *
 * @author bratseth
 */
public class Autoscaling {

    private final Status status;
    private final String description;
    private final Optional<ClusterResources> resources;
    private final Instant at;
    private final Load peak;
    private final Load ideal;
    private final Metrics metrics;

    public Autoscaling(Status status, String description, Optional<ClusterResources> resources, Instant at,
                       Load peak, Load ideal, Metrics metrics) {
        this.status = status;
        this.description = description;
        this.resources = resources;
        this.at = at;
        this.peak = peak;
        this.ideal = ideal;
        this.metrics = metrics;
    }

    public Status status() { return status; }

    public String description() { return description; }

    /** Returns the resource target of this, or empty if none (meaning keep the current allocation). */
    public Optional<ClusterResources> resources() {
        return resources;
    }

    /** Returns the time this was decided. */
    public Instant at() { return at; }

    /** Returns the peak load seen in the period considered in this. */
    public Load peak() { return peak; }

    /** Returns the ideal load the cluster in question should have. */
    public Load ideal() { return ideal; }

    public Metrics metrics() { return metrics; }

    public Autoscaling with(Status status, String description) {
        return new Autoscaling(status, description, resources, at, peak, ideal, metrics);
    }

    public Autoscaling withResources(Optional<ClusterResources> resources) {
        return new Autoscaling(status, description, resources, at, peak, ideal, metrics);
    }

    /** Converts this autoscaling into an ideal one at the completion of it. */
    public Autoscaling asIdeal(Instant at) {
        return new Autoscaling(Status.ideal,
                               "Cluster is ideally scaled within configured limits",
                               Optional.empty(),
                               at,
                               peak,
                               ideal,
                               metrics);
    }

    /** Returns true if no measurements was used in creating this instance. */
    public boolean isEmpty() {
        return peak.equals(Load.zero());
    }

    public boolean isPresent() {
        return ! isEmpty();
    }

    @Override
    public boolean equals(Object o) {
        if ( ! (o instanceof Autoscaling other)) return false;
        if ( ! this.status.equals(other.status)) return false;
        if ( ! this.description.equals(other.description)) return false;
        if ( ! this.resources.equals(other.resources)) return false;
        if ( ! this.at.equals(other.at)) return false;
        if ( ! this.peak.equals(other.peak)) return false;
        if ( ! this.ideal.equals(other.ideal)) return false;
        if ( ! this.metrics.equals(other.metrics)) return false;
        return true;
    }

    @Override
    public int hashCode() {
        return Objects.hash(status, description, at, peak, ideal, metrics);
    }

    @Override
    public String toString() {
        return (resources.isPresent() ? "Autoscaling to " + resources : "Don't autoscale") +
               (description.isEmpty() ? "" : ": " + description);
    }

    public static Autoscaling empty() {
        return empty("");
    }

    public static Autoscaling empty(String description) {
        return new Autoscaling(Status.unavailable,
                               description,
                               Optional.empty(),
                               Instant.EPOCH,
                               Load.zero(),
                               Load.zero(),
                               Metrics.zero());
    }

    /** Creates an autoscaling conclusion which does not change the current allocation for a specified reason. */
    public static Autoscaling dontScale(Status status, String description, ClusterModel clusterModel) {
        return new Autoscaling(status,
                               description,
                               Optional.empty(),
                               clusterModel.at(),
                               clusterModel.peakLoad(),
                               clusterModel.idealLoad(),
                               clusterModel.metrics());
    }

    /** Creates an autoscaling conclusion to scale. */
    public static Autoscaling scaleTo(ClusterResources target, ClusterModel clusterModel) {
        return new Autoscaling(Status.rescaling,
                               "Rescaling initiated due to load changes",
                               Optional.of(target),
                               clusterModel.at(),
                               clusterModel.peakLoad(),
                               clusterModel.idealLoad(),
                               clusterModel.metrics());
    }

    public enum Status {

        /** No status is available: Aautoscaling is disabled, or a brand new application. */
        unavailable,

        /** Autoscaling is not taking any action at the moment due to recent changes or a lack of data */
        waiting,

        /** The cluster is ideally scaled to the current load */
        ideal,

        /** The cluster should be rescaled further, but no better configuration is allowed by the current limits */
        insufficient,

        /** This cluster should be rescaled */
        rescaling

    }

    // Used to create BcpGroupInfo
    public record Metrics(double queryRate, double growthRateHeadroom, double cpuCostPerQuery) {

        public static Metrics zero() {
            return new Metrics(0, 0, 0);
        }

    }

}