blob: 150958835ac386a0205d0025321c01e2022ddb99 (
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
|
// 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.ClusterSpec;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* A list of metric snapshots from a cluster, sorted by increasing time (newest last).
*
* @author bratseth
*/
public class ClusterTimeseries {
private final ClusterSpec.Id cluster;
private final List<ClusterMetricSnapshot> snapshots;
ClusterTimeseries(ClusterSpec.Id cluster, List<ClusterMetricSnapshot> snapshots) {
this.cluster = cluster;
List<ClusterMetricSnapshot> sortedSnapshots = new ArrayList<>(snapshots);
Collections.sort(sortedSnapshots);
this.snapshots = Collections.unmodifiableList(sortedSnapshots);
}
public boolean isEmpty() { return snapshots.isEmpty(); }
public int size() { return snapshots.size(); }
public ClusterMetricSnapshot get(int index) { return snapshots.get(index); }
public List<ClusterMetricSnapshot> asList() { return snapshots; }
public ClusterSpec.Id cluster() { return cluster; }
public ClusterTimeseries add(ClusterMetricSnapshot snapshot) {
List<ClusterMetricSnapshot> list = new ArrayList<>(snapshots);
list.add(snapshot);
return new ClusterTimeseries(cluster, list);
}
/** The max query growth rate we can predict from this time-series as a fraction of the current traffic per minute */
public double maxQueryGrowthRate() {
if (snapshots.isEmpty()) return 0.1;
// Find the period having the highest growth rate, where total growth exceeds 30% increase
double maxGrowthRate = 0; // In query rate per minute
for (int start = 0; start < snapshots.size(); start++) {
if (start > 0) { // Optimization: Skip this point when starting from the previous is better relative to the best rate so far
Duration duration = durationBetween(start - 1, start);
if ( ! duration.isZero()) {
double growthRate = (queryRateAt(start - 1) - queryRateAt(start)) / duration.toMinutes();
if (growthRate >= maxGrowthRate)
continue;
}
}
for (int end = start + 1; end < snapshots.size(); end++) {
if (queryRateAt(end) >= queryRateAt(start) * 1.3) {
Duration duration = durationBetween(start, end);
if (duration.isZero()) continue;
double growthRate = (queryRateAt(end) - queryRateAt(start)) / duration.toMinutes();
if (growthRate > maxGrowthRate)
maxGrowthRate = growthRate;
}
}
}
if (maxGrowthRate == 0) { // No periods of significant growth
if (durationBetween(0, snapshots.size() - 1).toHours() < 24)
return 0.1; // ... because not much data
else
return 0.0; // ... because load is stable
}
if (queryRateNow() == 0) return 0.1; // Growth not expressible as a fraction of the current rate
return maxGrowthRate / queryRateNow();
}
/** The current query rate as a fraction of the peak rate in this timeseries */
public double currentQueryFractionOfMax() {
if (snapshots.isEmpty()) return 0.5;
var max = snapshots.stream().mapToDouble(ClusterMetricSnapshot::queryRate).max().getAsDouble();
return snapshots.get(snapshots.size() - 1).queryRate() / max;
}
private double queryRateAt(int index) {
return snapshots.get(index).queryRate();
}
private double queryRateNow() {
return queryRateAt(snapshots.size() - 1);
}
private Duration durationBetween(int startIndex, int endIndex) {
return Duration.between(snapshots.get(startIndex).at(), snapshots.get(endIndex).at());
}
}
|