Skip to content

Commit a422a70

Browse files
authored
chore: introduce Throughput and ThroughputSink (#2138)
_Pre-work_ In some upcoming work we want to be able to keep a running window of throughput performance in order to provide improved throughput. This PR introduces a new utility class to model and compute throughput, and the concept of a ThroughputSink which values can be appended to.
1 parent b318075 commit a422a70

File tree

7 files changed

+1224
-0
lines changed

7 files changed

+1224
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import com.google.common.base.MoreObjects;
20+
import java.time.Duration;
21+
import java.util.Objects;
22+
23+
/**
24+
* Convenience class to encapsulate the concept of a throughput value.
25+
*
26+
* <p>Given a number of bytes and a duration compute the number of bytes per second.
27+
*/
28+
final class Throughput {
29+
30+
private static final double NANOS_PER_SECOND = 1_000_000_000d;
31+
private final long numBytes;
32+
private final Duration duration;
33+
34+
// TODO: is there a efficient way we can limit precision without having to use BigDecimal?
35+
// Realistically, we don't need precision smaller than 1 byte per microsecond, leading to
36+
// 6 digits past the decimal of needed precision.
37+
private final double bytesPerSecond;
38+
39+
private Throughput(long numBytes, Duration duration) {
40+
this.numBytes = numBytes;
41+
this.duration = duration;
42+
this.bytesPerSecond = numBytes / (duration.toNanos() / NANOS_PER_SECOND);
43+
}
44+
45+
public long getNumBytes() {
46+
return numBytes;
47+
}
48+
49+
public Duration getDuration() {
50+
return duration;
51+
}
52+
53+
public double toBps() {
54+
return bytesPerSecond;
55+
}
56+
57+
public Throughput plus(Throughput other) {
58+
return new Throughput(this.numBytes + other.numBytes, this.duration.plus(other.duration));
59+
}
60+
61+
@Override
62+
public boolean equals(Object o) {
63+
if (this == o) {
64+
return true;
65+
}
66+
if (!(o instanceof Throughput)) {
67+
return false;
68+
}
69+
Throughput that = (Throughput) o;
70+
return Double.compare(that.bytesPerSecond, bytesPerSecond) == 0;
71+
}
72+
73+
@Override
74+
public int hashCode() {
75+
return Objects.hash(bytesPerSecond);
76+
}
77+
78+
@Override
79+
public String toString() {
80+
return MoreObjects.toStringHelper(this).add("bytesPerSecond", bytesPerSecond).toString();
81+
}
82+
83+
public static Throughput zero() {
84+
return new Throughput(0, Duration.ZERO);
85+
}
86+
87+
public static Throughput of(long numBytes, Duration duration) {
88+
return new Throughput(numBytes, duration);
89+
}
90+
91+
public static Throughput bytesPerSecond(long numBytes) {
92+
return new Throughput(numBytes, Duration.ofSeconds(1));
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import com.google.common.base.MoreObjects;
20+
import java.time.Duration;
21+
import java.time.Instant;
22+
import java.util.Comparator;
23+
import java.util.PriorityQueue;
24+
25+
/**
26+
* A simple moving window implementation which will keep a {@code window}s worth of Throughput
27+
* values and allow querying for the aggregate avg over that time window.
28+
*/
29+
final class ThroughputMovingWindow {
30+
31+
private final Duration window;
32+
33+
private final PriorityQueue<Entry> values;
34+
35+
private ThroughputMovingWindow(Duration window) {
36+
this.window = window;
37+
this.values = new PriorityQueue<>(Entry.COMP);
38+
}
39+
40+
void add(Instant now, Throughput value) {
41+
removeExpiredEntries(now);
42+
values.add(new Entry(now, value));
43+
}
44+
45+
Throughput avg(Instant now) {
46+
removeExpiredEntries(now);
47+
return values.stream()
48+
.map(Entry::getValue)
49+
.reduce(
50+
Throughput.zero(),
51+
(tp1, tp2) -> Throughput.of(tp1.getNumBytes() + tp2.getNumBytes(), window));
52+
}
53+
54+
private void removeExpiredEntries(Instant now) {
55+
Instant newMin = now.minus(window);
56+
values.removeIf(e -> lteq(e.getAt(), newMin));
57+
}
58+
59+
@Override
60+
public String toString() {
61+
return MoreObjects.toStringHelper(this)
62+
.add("window", window)
63+
.add("values.size()", values.size())
64+
.toString();
65+
}
66+
67+
static ThroughputMovingWindow of(Duration window) {
68+
return new ThroughputMovingWindow(window);
69+
}
70+
71+
private static boolean lteq(Instant a, Instant b) {
72+
return a.equals(b) || a.isBefore(b);
73+
}
74+
75+
private static final class Entry {
76+
private static final Comparator<Entry> COMP = Comparator.comparing(e -> e.at);
77+
private final Instant at;
78+
private final Throughput value;
79+
80+
private Entry(Instant at, Throughput value) {
81+
this.at = at;
82+
this.value = value;
83+
}
84+
85+
public Instant getAt() {
86+
return at;
87+
}
88+
89+
public Throughput getValue() {
90+
return value;
91+
}
92+
93+
@Override
94+
public String toString() {
95+
return MoreObjects.toStringHelper(this).add("at", at).add("value", value).toString();
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)